From 4c2b4863c175f699ed4a9048fb8cdd9af9cc481f Mon Sep 17 00:00:00 2001 From: julian merlo Date: Sat, 2 Mar 2024 17:03:29 -0300 Subject: [PATCH 01/30] First version --- blockchain-watcher/config/default.json | 9 +- blockchain-watcher/config/mainnet.json | 4 + .../actions/aptos/HandleAptosTransactions.ts | 39 +++++ .../actions/aptos/PollAptosTransactions.ts | 135 ++++++++++++++++++ .../domain/actions/sui/PollSuiTransactions.ts | 5 +- .../src/domain/entities/aptos.ts | 13 ++ blockchain-watcher/src/domain/repositories.ts | 8 ++ .../aptos/aptosLogMessagePublishedMapper.ts | 13 ++ .../aptosRedeemedTransactionFoundMapper.ts | 14 ++ .../repositories/RepositoriesBuilder.ts | 60 ++++++-- .../repositories/StaticJobRepository.ts | 30 ++++ .../aptos/AptosJsonRPCBlockRepository.ts | 85 +++++++++++ .../RateLimitedAptosJsonRPCBlockRepository.ts | 25 ++++ .../rpc/http/InstrumentedAptosProvider.ts | 77 ++++++++++ 14 files changed, 498 insertions(+), 19 deletions(-) create mode 100644 blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts create mode 100644 blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts create mode 100644 blockchain-watcher/src/domain/entities/aptos.ts create mode 100644 blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts create mode 100644 blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts create mode 100644 blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts create mode 100644 blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts create mode 100644 blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts diff --git a/blockchain-watcher/config/default.json b/blockchain-watcher/config/default.json index f331434ad..876ea98a8 100644 --- a/blockchain-watcher/config/default.json +++ b/blockchain-watcher/config/default.json @@ -3,7 +3,7 @@ "port": 9090, "logLevel": "debug", "dryRun": true, - "enabledPlatforms": ["solana", "evm", "sui"], + "enabledPlatforms": ["solana", "evm", "sui", "aptos"], "sns": { "topicArn": "arn:aws:sns:us-east-1:000000000000:localstack-topic.fifo", "region": "us-east-1", @@ -131,6 +131,13 @@ "rpcs": ["https://fullnode.testnet.sui.io:443"], "timeout": 10000 }, + "aptos": { + "name": "aptos", + "network": "testnet", + "chainId": 22, + "rpcs": [""], + "timeout": 10000 + }, "arbitrum": { "name": "arbitrum", "network": "goerli", diff --git a/blockchain-watcher/config/mainnet.json b/blockchain-watcher/config/mainnet.json index 02fc2ae6a..c8c5eb2ad 100644 --- a/blockchain-watcher/config/mainnet.json +++ b/blockchain-watcher/config/mainnet.json @@ -118,6 +118,10 @@ "sui": { "network": "mainnet", "rpcs": ["https://fullnode.mainnet.sui.io:443", "http://m-sui-01.nodes.stable.io:8546"] + }, + "aptos": { + "network": "mainnet", + "rpcs": ["https://fullnode.mainnet.aptoslabs.com"] } } } diff --git a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts new file mode 100644 index 000000000..a058a5c0d --- /dev/null +++ b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts @@ -0,0 +1,39 @@ +import { TransactionFoundEvent } from "../../entities"; +import { StatRepository } from "../../repositories"; +import { AptosEvent } from "../../entities/aptos"; + +export class HandleAptosTransactions { + constructor( + private readonly cfg: HandleAptosTransactionsOptions, + private readonly mapper: (tx: AptosEvent) => TransactionFoundEvent, + private readonly target: (parsed: TransactionFoundEvent[]) => Promise, + private readonly statsRepo: StatRepository + ) {} + + public async handle(txs: AptosEvent[]): Promise { + const items: TransactionFoundEvent[] = []; + await this.target(items); + + return items; + } + + private report(protocol: string) { + if (!this.cfg.metricName) return; + + const labels = this.cfg.metricLabels ?? { + job: this.cfg.id, + chain: "aptos", + commitment: "finalized", + protocol: protocol ?? "unknown", + }; + + this.statsRepo.count(this.cfg.metricName, labels); + } +} + +export interface HandleAptosTransactionsOptions { + metricLabels?: { job: string; chain: string; commitment: string }; + eventTypes?: string[]; + metricName?: string; + id: string; +} diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts new file mode 100644 index 000000000..d299f021f --- /dev/null +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -0,0 +1,135 @@ +import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; +import { TransactionFilter } from "@mysten/sui.js/client"; +import winston, { Logger } from "winston"; +import { RunPollingJob } from "../RunPollingJob"; + +/** + * Instead of fetching block per block and scanning for transactions, + * this poller uses the queryTransactions function from the sui sdk + * to set up a filter and retrieve all transactions that match it, + * thus avoiding the need to scan blocks which may not have any + * valuable information. + * + * Each queryTransactions request accepts a cursor parameter, which + * is the digest of the last transaction of the previous page. + */ +export class PollAptosTransactions extends RunPollingJob { + protected readonly logger: Logger; + + private latestSequenceNumber?: number; + private sequenceHeightCursor?: number; + + constructor( + private readonly cfg: PollAptosTransactionsConfig, + private readonly statsRepo: StatRepository, + private readonly metadataRepo: MetadataRepository, + private readonly repo: AptosRepository + ) { + super(cfg.id, statsRepo, cfg.interval); + this.logger = winston.child({ module: "PollSui", label: this.cfg.id }); + } + + protected async preHook(): Promise { + const metadata = await this.metadataRepo.get(this.cfg.id); + if (metadata) { + this.sequenceHeightCursor = Number(metadata.lastSequence!); + } + } + + protected async hasNext(): Promise { + return true; + } + + protected async get(): Promise { + const range = this.getBlockRange(); + + const events = await this.repo.getSequenceNumber(range); + + this.latestSequenceNumber = Number(events[events.length - 1].sequence_number); + + const transactions = await this.repo.getTransactionsForVersions(events); + + return transactions; + } + protected async persist(): Promise { + this.latestSequenceNumber = this.latestSequenceNumber; + if (this.latestSequenceNumber) { + await this.metadataRepo.save(this.cfg.id, { lastSequence: this.latestSequenceNumber }); + } + } + + protected report(): void {} + + /** + * Get the block range to extract. + * @param latestBlockHeight - the latest known height of the chain + * @returns an always valid range, in the sense from is always <= to + */ + private getBlockRange(): Sequence | undefined { + if (this.latestSequenceNumber) { + // check that it's not prior to the range start + if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.latestSequenceNumber) { + return { + fromSequence: Number(this.latestSequenceNumber), + toSequence: Number(this.latestSequenceNumber) + this.cfg.getBlockBatchSize(), + }; + } + } + } +} + +export class PollAptosTransactionsConfig { + constructor(private readonly props: PollAptosTransactionsConfigProps) {} + + public getBlockBatchSize() { + return this.props.blockBatchSize ?? 100; + } + + public getCommitment() { + return this.props.commitment ?? "latest"; + } + + public get id(): string { + return this.props.id; + } + + public get interval(): number | undefined { + return this.props.interval; + } + + public get fromSequence(): bigint | undefined { + return this.props.fromSequence ? BigInt(this.props.fromSequence) : undefined; + } + + public get toSequence(): bigint | undefined { + return this.props.toSequence ? BigInt(this.props.toSequence) : undefined; + } + + public get filter(): TransactionFilter | undefined { + return this.props.filter; + } +} + +interface PollAptosTransactionsConfigProps { + fromSequence?: bigint; + toSequence?: bigint; + blockBatchSize?: number; + commitment?: string; + interval?: number; + addresses: string[]; + topics: string[]; + id: string; + chain: string; + chainId: number; + environment: string; + filter?: TransactionFilter; +} + +type PollAptosTransactionsMetadata = { + lastSequence?: number; +}; + +export type Sequence = { + fromSequence: number; + toSequence: number; +}; diff --git a/blockchain-watcher/src/domain/actions/sui/PollSuiTransactions.ts b/blockchain-watcher/src/domain/actions/sui/PollSuiTransactions.ts index 2ede0498c..ae3e68773 100644 --- a/blockchain-watcher/src/domain/actions/sui/PollSuiTransactions.ts +++ b/blockchain-watcher/src/domain/actions/sui/PollSuiTransactions.ts @@ -1,7 +1,6 @@ -import { SuiEventFilter, TransactionFilter } from "@mysten/sui.js/client"; -import winston, { Logger } from "winston"; - import { MetadataRepository, StatRepository, SuiRepository } from "../../repositories"; +import { TransactionFilter } from "@mysten/sui.js/client"; +import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; /** diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts new file mode 100644 index 000000000..f32a487a3 --- /dev/null +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -0,0 +1,13 @@ +import { Types } from "aptos"; + +export type AptosEvent = Omit & { + version: string; + data: { + consistency_level: number; + nonce: string; + payload: string; + sender: string; + sequence: string; + timestamp: string; + }; +}; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index aa53f3f1d..56dfbf8a9 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -18,6 +18,9 @@ import { import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; +import { AptosEvent } from "./entities/aptos"; +import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { Sequence } from "./actions/aptos/PollAptosTransactions"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -67,6 +70,11 @@ export interface SuiRepository { ): Promise; } +export interface AptosRepository { + getSequenceNumber(range: Sequence | undefined): Promise; + getTransactionsForVersions(events: AptosEvent[]): Promise; +} + export interface MetadataRepository { get(id: string): Promise; save(id: string, metadata: Metadata): Promise; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts new file mode 100644 index 000000000..40a09a511 --- /dev/null +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -0,0 +1,13 @@ +import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; +import { AptosEvent } from "../../../domain/entities/aptos"; +import winston from "winston"; + +let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); + +const SOURCE_EVENT_TAIL = "::state::WormholeMessage"; + +export const aptosLogMessagePublishedMapper = ( + tx: AptosEvent[] +): LogFoundEvent | undefined => { + return undefined; +}; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts new file mode 100644 index 000000000..450a008f1 --- /dev/null +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -0,0 +1,14 @@ +import { TransactionFoundEvent } from "../../../domain/entities"; +import { AptosEvent } from "../../../domain/entities/aptos"; +import winston from "winston"; + +let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFoundMapper" }); + +const REDEEM_EVENT_TAIL = "::complete_transfer::TransferRedeemed"; +const APTOS_CHAIN = "aptos"; + +export const aptosRedeemedTransactionFoundMapper = ( + tx: AptosEvent[] +): TransactionFoundEvent | undefined => { + return undefined; +}; diff --git a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts index c9aaf70ed..b28d10ff2 100644 --- a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts +++ b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts @@ -1,34 +1,39 @@ +import { AptosRepository, JobRepository, SuiRepository } from "../../domain/repositories"; +import { RateLimitedAptosJsonRPCBlockRepository } from "./aptos/RateLimitedAptosJsonRPCBlockRepository"; +import { RateLimitedEvmJsonRPCBlockRepository } from "./evm/RateLimitedEvmJsonRPCBlockRepository"; +import { RateLimitedSuiJsonRPCBlockRepository } from "./sui/RateLimitedSuiJsonRPCBlockRepository"; +import { AptosJsonRPCBlockRepository } from "./aptos/AptosJsonRPCBlockRepository"; import { SNSClient, SNSClientConfig } from "@aws-sdk/client-sns"; +import { InstrumentedAptosProvider } from "../rpc/http/InstrumentedAptosProvider"; +import { InstrumentedHttpProvider } from "../rpc/http/InstrumentedHttpProvider"; +import { Config } from "../config"; import { InstrumentedConnection, InstrumentedSuiClient, - RpcConfig, providerPoolSupplier, + RpcConfig, } from "@xlabs/rpc-pool"; import { + MoonbeamEvmJsonRPCBlockRepository, ArbitrumEvmJsonRPCBlockRepository, + RateLimitedSolanaSlotRepository, + PolygonJsonRPCBlockRepository, BscEvmJsonRPCBlockRepository, - EvmJsonRPCBlockRepository, EvmJsonRPCBlockRepositoryCfg, + EvmJsonRPCBlockRepository, + SuiJsonRPCBlockRepository, + Web3SolanaSlotRepository, FileMetadataRepository, - MoonbeamEvmJsonRPCBlockRepository, - PolygonJsonRPCBlockRepository, + StaticJobRepository, PromStatRepository, - ProviderPoolMap, - RateLimitedSolanaSlotRepository, SnsEventRepository, - StaticJobRepository, - SuiJsonRPCBlockRepository, - Web3SolanaSlotRepository, + ProviderPoolMap, } from "."; -import { JobRepository, SuiRepository } from "../../domain/repositories"; -import { Config } from "../config"; -import { InstrumentedHttpProvider } from "../rpc/http/InstrumentedHttpProvider"; -import { RateLimitedEvmJsonRPCBlockRepository } from "./evm/RateLimitedEvmJsonRPCBlockRepository"; -import { RateLimitedSuiJsonRPCBlockRepository } from "./sui/RateLimitedSuiJsonRPCBlockRepository"; const SOLANA_CHAIN = "solana"; +const APTOS_CHAIN = "aptos"; const EVM_CHAIN = "evm"; +const SUI_CHAIN = "sui"; const EVM_CHAINS = new Map([ ["ethereum", "evmRepo"], ["ethereum-sepolia", "evmRepo"], @@ -50,7 +55,6 @@ const EVM_CHAINS = new Map([ ["polygon", "polygon-evmRepo"], ["ethereum-holesky", "evmRepo"], ]); -const SUI_CHAIN = "sui"; const POOL_STRATEGY = "weighted"; @@ -134,6 +138,16 @@ export class RepositoriesBuilder { this.repositories.set("sui-repo", suiRepository); } + + if (chain === APTOS_CHAIN) { + const aptosRepository = new RateLimitedAptosJsonRPCBlockRepository( + new AptosJsonRPCBlockRepository( + this.createAptosClient(chain, this.cfg.chains[chain].rpcs[0]) + ) + ); + + this.repositories.set("aptos-repo", aptosRepository); + } }); this.repositories.set( @@ -149,6 +163,7 @@ export class RepositoriesBuilder { snsRepo: this.getSnsEventRepository(), solanaSlotRepo: this.getSolanaSlotRepository(), suiRepo: this.getSuiRepository(), + aptosRepo: this.getAptosRepository(), } ) ); @@ -184,6 +199,10 @@ export class RepositoriesBuilder { return this.getRepo("sui-repo"); } + public getAptosRepository(): AptosRepository { + return this.getRepo("aptos-repo"); + } + private getRepo(name: string): any { const repo = this.repositories.get(name); if (!repo) throw new Error(`No repository ${name}`); @@ -231,4 +250,15 @@ export class RepositoriesBuilder { maxDelay: 30_000, }); } + + private createAptosClient(chain: string, url: string): InstrumentedAptosProvider { + return new InstrumentedAptosProvider({ + chain, + url, + retries: 3, + timeout: 1_0000, + initialDelay: 1_000, + maxDelay: 30_000, + }); + } } diff --git a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts index 7b32a79a9..b616e201a 100644 --- a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts @@ -9,6 +9,7 @@ import { } from "../../domain/actions"; import { JobDefinition, Handler, LogFoundEvent } from "../../domain/entities"; import { + AptosRepository, EvmBlockRepository, JobRepository, MetadataRepository, @@ -33,6 +34,11 @@ import { PollSuiTransactions, PollSuiTransactionsConfig, } from "../../domain/actions/sui/PollSuiTransactions"; +import { + PollAptosTransactions, + PollAptosTransactionsConfig, +} from "../../domain/actions/aptos/PollAptosTransactions"; +import { HandleAptosTransactions } from "../../domain/actions/aptos/HandleAptosTransactions"; export class StaticJobRepository implements JobRepository { private fileRepo: FileMetadataRepository; @@ -49,6 +55,7 @@ export class StaticJobRepository implements JobRepository { private snsRepo: SnsEventRepository; private solanaSlotRepo: SolanaSlotRepository; private suiRepo: SuiRepository; + private aptosRepo: AptosRepository; constructor( environment: string, @@ -61,6 +68,7 @@ export class StaticJobRepository implements JobRepository { snsRepo: SnsEventRepository; solanaSlotRepo: SolanaSlotRepository; suiRepo: SuiRepository; + aptosRepo: AptosRepository; } ) { this.fileRepo = new FileMetadataRepository(path); @@ -70,6 +78,7 @@ export class StaticJobRepository implements JobRepository { this.snsRepo = repos.snsRepo; this.solanaSlotRepo = repos.solanaSlotRepo; this.suiRepo = repos.suiRepo; + this.aptosRepo = repos.aptosRepo; this.environment = environment; this.dryRun = dryRun; this.fill(); @@ -144,9 +153,18 @@ export class StaticJobRepository implements JobRepository { this.metadataRepo, this.suiRepo ); + + const pollAptosTransactions = (jobDef: JobDefinition) => + new PollAptosTransactions( + new PollAptosTransactionsConfig({ ...jobDef.source.config, id: jobDef.id }), + this.statsRepo, + this.metadataRepo, + this.aptosRepo + ); this.sources.set("PollEvm", pollEvm); this.sources.set("PollSolanaTransactions", pollSolanaTransactions); this.sources.set("PollSuiTransactions", pollSuiTransactions); + this.sources.set("PollAptosTransactions", pollAptosTransactions); // Mappers this.mappers.set("evmLogMessagePublishedMapper", evmLogMessagePublishedMapper); @@ -205,10 +223,22 @@ export class StaticJobRepository implements JobRepository { return instance.handle.bind(instance); }; + const handleAptosTx = async (config: any, target: string, mapper: any) => { + const instance = new HandleAptosTransactions( + config, + mapper, + await this.getTarget(target), + this.statsRepo + ); + + return instance.handle.bind(instance); + }; + this.handlers.set("HandleEvmLogs", handleEvmLogs); this.handlers.set("HandleEvmTransactions", handleEvmTransactions); this.handlers.set("HandleSolanaTransactions", handleSolanaTx); this.handlers.set("HandleSuiTransactions", handleSuiTx); + this.handlers.set("HandleAptosTransactions", handleAptosTx); } private async getTarget(target: string): Promise<(items: any[]) => Promise> { diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts new file mode 100644 index 000000000..fe70c5647 --- /dev/null +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -0,0 +1,85 @@ +import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; +import { coalesceChainId } from "@certusone/wormhole-sdk/lib/cjs/utils/consts"; +import { AptosEvent } from "../../../domain/entities/aptos"; +import { Sequence } from "../../../domain/actions/aptos/PollAptosTransactions"; +import winston from "winston"; + +const CORE_BRIDGE_ADDRESS = "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"; +const EVENT_HANDLE = `${CORE_BRIDGE_ADDRESS}::state::WormholeMessageHandle`; +const FIELD_NAME = "event"; + +export class AptosJsonRPCBlockRepository { + private readonly logger: winston.Logger; + + constructor(private readonly client: InstrumentedAptosProvider) { + this.logger = winston.child({ module: "AptossonRPCBlockRepository" }); + } + + async getSequenceNumber(range: Sequence | undefined): Promise { + try { + const fromSequence = range?.fromSequence ? Number(range?.fromSequence) : undefined; + const toSequence = range?.toSequence + ? Number(range?.toSequence) - Number(range?.fromSequence) + 1 + : undefined; + + const results = await this.client.getEventsByEventHandle( + CORE_BRIDGE_ADDRESS, + EVENT_HANDLE, + FIELD_NAME, + fromSequence, + toSequence + ); + return results; + } catch (e) { + this.handleError(e, "getSequenceNumber"); + throw e; + } + } + + async getTransactionsForVersions(events: AptosEvent[]): Promise { + const transactionsByVersion: TransactionsByVersion[] = []; + + for (const event of events) { + const result = await this.client.getTransactionByVersion(Number(event.version)); + const tx = { + consistencyLevel: event.data.consistency_level, + timestamp: result.timestamp, + sequence: result.sequence_number, + version: result.version, + payload: result.payload, + sender: result.sender, + status: result.success, + events: result.events, + nonce: event.data.nonce, + hash: result.hash, + }; + transactionsByVersion.push(tx); + } + + return transactionsByVersion; + } + + private handleError(e: any, method: string) { + this.logger.error(`[sui] Error calling ${method}: ${e.message}`); + } +} + +type ResultBlocksHeight = { + sequence_number: bigint; +}; + +export type TransactionsByVersion = { + consistencyLevel?: number; + timestamp?: string; + sequence?: string; + version?: string; + payload?: string; + sender?: string; + status: boolean; + events: any; + nonce?: string; + hash: string; +}; + +const makeVaaKey = (transactionHash: string, emitter: string, seq: string): string => + `${transactionHash}:${coalesceChainId("aptos")}/${emitter}/${seq}`; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts new file mode 100644 index 000000000..67cfe182f --- /dev/null +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -0,0 +1,25 @@ +import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; +import { TransactionsByVersion } from "./AptosJsonRPCBlockRepository"; +import { AptosRepository } from "../../../domain/repositories"; +import { AptosEvent } from "../../../domain/entities/aptos"; +import { Options } from "../common/rateLimitedOptions"; +import { Sequence } from "../../../domain/actions/aptos/PollAptosTransactions"; +import winston from "winston"; + +export class RateLimitedAptosJsonRPCBlockRepository + extends RateLimitedRPCRepository + implements AptosRepository +{ + constructor(delegate: AptosRepository, opts: Options = { period: 10_000, limit: 1000 }) { + super(delegate, opts); + this.logger = winston.child({ module: "RateLimitedAptosJsonRPCBlockRepository" }); + } + + getSequenceNumber(range: Sequence | undefined): Promise { + return this.breaker.fn(() => this.delegate.getSequenceNumber(range)).execute(); + } + + getTransactionsForVersions(events: AptosEvent[]): Promise { + return this.breaker.fn(() => this.delegate.getTransactionsForVersions(events)).execute(); + } +} diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts new file mode 100644 index 000000000..f23c67a26 --- /dev/null +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -0,0 +1,77 @@ +import { AptosClient } from "aptos"; +import winston from "winston"; + +// make url and chain required +type InstrumentedAptosProviderOptions = Required> & + HttpClientOptions; + +/** + * A simple HTTP client with exponential backoff retries and 429 handling. + */ +export class InstrumentedAptosProvider { + private initialDelay: number = 1_000; + private maxDelay: number = 60_000; + private retries: number = 0; + private timeout: number = 5_000; + private url: string; + private chain: string; + client: AptosClient; + + private logger: winston.Logger = winston.child({ module: "InstrumentedAptosProvider" }); + + constructor(options: InstrumentedAptosProviderOptions) { + options?.initialDelay && (this.initialDelay = options.initialDelay); + options?.maxDelay && (this.maxDelay = options.maxDelay); + options?.retries && (this.retries = options.retries); + options?.timeout && (this.timeout = options.timeout); + + if (!options.url) throw new Error("URL is required"); + this.url = options.url; + + if (!options.chain) throw new Error("Chain is required"); + this.chain = options.chain; + + this.client = new AptosClient(this.url); + } + + public async getEventsByEventHandle( + address: string, + eventHandle: string, + fieldName: string, + fromSequence?: number, + toSequence: number = 100 + ): Promise { + try { + const params = fromSequence + ? { start: fromSequence, limit: toSequence } + : { limit: toSequence }; + const result = await this.client.getEventsByEventHandle( + address, + eventHandle, + fieldName, + params + ); + return result; + } catch (e) { + throw e; + } + } + + public async getTransactionByVersion(version: number): Promise { + try { + const result = await this.client.getTransactionByVersion(version); + return result; + } catch (e) { + throw e; + } + } +} + +export type HttpClientOptions = { + chain?: string; + url?: string; + initialDelay?: number; + maxDelay?: number; + retries?: number; + timeout?: number; +}; From 5d100be49ec296a19458a047cfeff0afd4c9dea8 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 4 Mar 2024 09:55:41 -0300 Subject: [PATCH 02/30] Improve index --- .../actions/aptos/PollAptosTransactions.ts | 94 ++++++++++++++----- .../src/domain/actions/evm/PollEvm.ts | 14 +-- .../aptos/AptosJsonRPCBlockRepository.ts | 6 +- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts index d299f021f..2c45f013f 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -16,8 +16,9 @@ import { RunPollingJob } from "../RunPollingJob"; export class PollAptosTransactions extends RunPollingJob { protected readonly logger: Logger; - private latestSequenceNumber?: number; - private sequenceHeightCursor?: number; + private lastSequence?: bigint; + private sequenceHeightCursor?: bigint; + private previousSequence?: bigint; constructor( private readonly cfg: PollAptosTransactionsConfig, @@ -26,17 +27,30 @@ export class PollAptosTransactions extends RunPollingJob { private readonly repo: AptosRepository ) { super(cfg.id, statsRepo, cfg.interval); - this.logger = winston.child({ module: "PollSui", label: this.cfg.id }); + this.logger = winston.child({ module: "PollAptos", label: this.cfg.id }); } protected async preHook(): Promise { const metadata = await this.metadataRepo.get(this.cfg.id); if (metadata) { - this.sequenceHeightCursor = Number(metadata.lastSequence!); + this.sequenceHeightCursor = metadata.lastSequence; + this.previousSequence = metadata.previousSequence; + this.lastSequence = metadata.lastSequence; } } protected async hasNext(): Promise { + if ( + this.cfg.toSequence && + this.sequenceHeightCursor && + this.sequenceHeightCursor >= BigInt(this.cfg.toSequence) + ) { + this.logger.info( + `[aptos][PollAptosTransactions] Finished processing all transactions from sequence ${this.cfg.fromSequence} to ${this.cfg.toSequence}` + ); + return false; + } + return true; } @@ -45,37 +59,72 @@ export class PollAptosTransactions extends RunPollingJob { const events = await this.repo.getSequenceNumber(range); - this.latestSequenceNumber = Number(events[events.length - 1].sequence_number); + // save preveous sequence with last sequence and update last sequence with the new sequence + this.previousSequence = this.lastSequence; + this.lastSequence = BigInt(events[events.length - 1].sequence_number); + + if (this.previousSequence && this.lastSequence && this.previousSequence === this.lastSequence) { + return []; + } const transactions = await this.repo.getTransactionsForVersions(events); return transactions; } - protected async persist(): Promise { - this.latestSequenceNumber = this.latestSequenceNumber; - if (this.latestSequenceNumber) { - await this.metadataRepo.save(this.cfg.id, { lastSequence: this.latestSequenceNumber }); + + private getBlockRange(): Sequence | undefined { + if (this.previousSequence && this.lastSequence && this.previousSequence === this.lastSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, + }; } - } - protected report(): void {} + if (this.previousSequence && this.lastSequence && this.previousSequence !== this.lastSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: Number(this.lastSequence) - Number(this.previousSequence), + }; + } - /** - * Get the block range to extract. - * @param latestBlockHeight - the latest known height of the chain - * @returns an always valid range, in the sense from is always <= to - */ - private getBlockRange(): Sequence | undefined { - if (this.latestSequenceNumber) { + if (this.lastSequence) { // check that it's not prior to the range start - if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.latestSequenceNumber) { + if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.lastSequence) { return { - fromSequence: Number(this.latestSequenceNumber), - toSequence: Number(this.latestSequenceNumber) + this.cfg.getBlockBatchSize(), + fromSequence: Number(this.lastSequence), + toSequence: + Number(this.lastSequence) + this.cfg.getBlockBatchSize() - Number(this.lastSequence), }; } } } + + protected async persist(): Promise { + this.lastSequence = this.lastSequence; + if (this.lastSequence) { + await this.metadataRepo.save(this.cfg.id, { + lastSequence: this.lastSequence, + previousSequence: this.previousSequence, + }); + } + } + + protected report(): void { + const labels = { + job: this.cfg.id, + chain: "aptos", + commitment: this.cfg.getCommitment(), + }; + this.statsRepo.count("job_execution", labels); + this.statsRepo.measure("polling_cursor", this.lastSequence ?? 0n, { + ...labels, + type: "max", + }); + this.statsRepo.measure("polling_cursor", this.lastSequence ?? 0n, { + ...labels, + type: "current", + }); + } } export class PollAptosTransactionsConfig { @@ -126,7 +175,8 @@ interface PollAptosTransactionsConfigProps { } type PollAptosTransactionsMetadata = { - lastSequence?: number; + lastSequence?: bigint; + previousSequence?: bigint; }; export type Sequence = { diff --git a/blockchain-watcher/src/domain/actions/evm/PollEvm.ts b/blockchain-watcher/src/domain/actions/evm/PollEvm.ts index a55fdc75e..ab23dd6ad 100644 --- a/blockchain-watcher/src/domain/actions/evm/PollEvm.ts +++ b/blockchain-watcher/src/domain/actions/evm/PollEvm.ts @@ -15,7 +15,7 @@ export class PollEvm extends RunPollingJob { private readonly blockRepo: EvmBlockRepository; private readonly metadataRepo: MetadataRepository; - private readonly statsRepository: StatRepository; + private readonly statsRepo: StatRepository; private readonly getEvm: GetEvmLogs; private cfg: PollEvmLogsConfig; @@ -30,14 +30,14 @@ export class PollEvm extends RunPollingJob { constructor( blockRepo: EvmBlockRepository, metadataRepo: MetadataRepository, - statsRepository: StatRepository, + statsRepo: StatRepository, cfg: PollEvmLogsConfig, getEvm: string ) { - super(cfg.id, statsRepository, cfg.interval); + super(cfg.id, statsRepo, cfg.interval); this.blockRepo = blockRepo; this.metadataRepo = metadataRepo; - this.statsRepository = statsRepository; + this.statsRepo = statsRepo; this.cfg = cfg; this.logger = winston.child({ module: "PollEvm", label: this.cfg.id }); this.getEvm = new this.getEvmRecords[getEvm ?? "GetEvmLogs"](blockRepo); @@ -129,12 +129,12 @@ export class PollEvm extends RunPollingJob { chain: this.cfg.chain ?? "", commitment: this.cfg.getCommitment(), }; - this.statsRepository.count("job_execution", labels); - this.statsRepository.measure("polling_cursor", this.latestBlockHeight ?? 0n, { + this.statsRepo.count("job_execution", labels); + this.statsRepo.measure("polling_cursor", this.latestBlockHeight ?? 0n, { ...labels, type: "max", }); - this.statsRepository.measure("polling_cursor", this.blockHeightCursor ?? 0n, { + this.statsRepo.measure("polling_cursor", this.blockHeightCursor ?? 0n, { ...labels, type: "current", }); diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index fe70c5647..76e74d3f3 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -18,9 +18,7 @@ export class AptosJsonRPCBlockRepository { async getSequenceNumber(range: Sequence | undefined): Promise { try { const fromSequence = range?.fromSequence ? Number(range?.fromSequence) : undefined; - const toSequence = range?.toSequence - ? Number(range?.toSequence) - Number(range?.fromSequence) + 1 - : undefined; + const toSequence = range?.toSequence ? Number(range?.toSequence) : undefined; const results = await this.client.getEventsByEventHandle( CORE_BRIDGE_ADDRESS, @@ -60,7 +58,7 @@ export class AptosJsonRPCBlockRepository { } private handleError(e: any, method: string) { - this.logger.error(`[sui] Error calling ${method}: ${e.message}`); + this.logger.error(`[aptos] Error calling ${method}: ${e.message}`); } } From 0bd1d8764c0afc1dafa3e1c517a0611ace4762f6 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 4 Mar 2024 13:46:13 -0300 Subject: [PATCH 03/30] Improve repository and mapper --- .../actions/aptos/HandleAptosTransactions.ts | 13 +++- .../actions/aptos/PollAptosTransactions.ts | 68 ++++++++++++------- blockchain-watcher/src/domain/repositories.ts | 9 ++- .../aptos/aptosLogMessagePublishedMapper.ts | 32 +++++++-- .../mappers/contractsMapperConfig.json | 19 ++++++ .../repositories/StaticJobRepository.ts | 13 +++- .../aptos/AptosJsonRPCBlockRepository.ts | 58 +++++++++------- .../RateLimitedAptosJsonRPCBlockRepository.ts | 15 ++-- .../rpc/http/InstrumentedAptosProvider.ts | 9 +++ 9 files changed, 167 insertions(+), 69 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts index a058a5c0d..61b26afc0 100644 --- a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts @@ -1,17 +1,24 @@ import { TransactionFoundEvent } from "../../entities"; +import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { StatRepository } from "../../repositories"; -import { AptosEvent } from "../../entities/aptos"; export class HandleAptosTransactions { constructor( private readonly cfg: HandleAptosTransactionsOptions, - private readonly mapper: (tx: AptosEvent) => TransactionFoundEvent, + private readonly mapper: (tx: TransactionsByVersion) => TransactionFoundEvent, private readonly target: (parsed: TransactionFoundEvent[]) => Promise, private readonly statsRepo: StatRepository ) {} - public async handle(txs: AptosEvent[]): Promise { + public async handle(txs: TransactionsByVersion[]): Promise { const items: TransactionFoundEvent[] = []; + + for (const tx of txs) { + const txMapped = this.mapper(tx); + this.report(txMapped.attributes.protocol); + items.push(txMapped); + } + await this.target(items); return items; diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts index 2c45f013f..aabfae065 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -1,5 +1,4 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; -import { TransactionFilter } from "@mysten/sui.js/client"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; @@ -55,11 +54,12 @@ export class PollAptosTransactions extends RunPollingJob { } protected async get(): Promise { + const filter = this.cfg.filter; const range = this.getBlockRange(); - const events = await this.repo.getSequenceNumber(range); + const events = await this.repo.getSequenceNumber(range, filter); - // save preveous sequence with last sequence and update last sequence with the new sequence + // save previous sequence with last sequence and update last sequence with the new sequence this.previousSequence = this.lastSequence; this.lastSequence = BigInt(events[events.length - 1].sequence_number); @@ -67,28 +67,32 @@ export class PollAptosTransactions extends RunPollingJob { return []; } - const transactions = await this.repo.getTransactionsForVersions(events); + const transactions = await this.repo.getTransactionsForVersions(events, filter); return transactions; } private getBlockRange(): Sequence | undefined { - if (this.previousSequence && this.lastSequence && this.previousSequence === this.lastSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, - }; - } + if (this.previousSequence && this.lastSequence) { + // if process the [same sequence], return the same last sequence and the to sequence equal 1 + if (this.previousSequence === this.lastSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, + }; + } - if (this.previousSequence && this.lastSequence && this.previousSequence !== this.lastSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: Number(this.lastSequence) - Number(this.previousSequence), - }; + // if process [different sequences], return the difference between the last sequence and the previous sequence plus 1 + if (this.previousSequence !== this.lastSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, + }; + } } if (this.lastSequence) { - // check that it's not prior to the range start + // if there is [no previous sequence], return the last sequence and the to sequence equal the block batch size if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.lastSequence) { return { fromSequence: Number(this.lastSequence), @@ -97,6 +101,14 @@ export class PollAptosTransactions extends RunPollingJob { }; } } + + // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size + if (this.cfg.fromSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: this.cfg.getBlockBatchSize(), + }; + } } protected async persist(): Promise { @@ -154,29 +166,35 @@ export class PollAptosTransactionsConfig { return this.props.toSequence ? BigInt(this.props.toSequence) : undefined; } - public get filter(): TransactionFilter | undefined { + public get filter(): TransactionFilter { return this.props.filter; } } -interface PollAptosTransactionsConfigProps { +export interface PollAptosTransactionsConfigProps { + blockBatchSize?: number; fromSequence?: bigint; toSequence?: bigint; - blockBatchSize?: number; + environment: string; commitment?: string; - interval?: number; addresses: string[]; + interval?: number; topics: string[]; - id: string; - chain: string; chainId: number; - environment: string; - filter?: TransactionFilter; + filter: TransactionFilter; + chain: string; + id: string; } type PollAptosTransactionsMetadata = { - lastSequence?: bigint; previousSequence?: bigint; + lastSequence?: bigint; +}; + +export type TransactionFilter = { + fieldName: string; + address: string; + event: string; }; export type Sequence = { diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 56dfbf8a9..7f4f96f54 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -20,7 +20,7 @@ import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { AptosEvent } from "./entities/aptos"; import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -import { Sequence } from "./actions/aptos/PollAptosTransactions"; +import { Sequence, TransactionFilter } from "./actions/aptos/PollAptosTransactions"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -71,8 +71,11 @@ export interface SuiRepository { } export interface AptosRepository { - getSequenceNumber(range: Sequence | undefined): Promise; - getTransactionsForVersions(events: AptosEvent[]): Promise; + getSequenceNumber(range: Sequence | undefined, filter: TransactionFilter): Promise; + getTransactionsForVersions( + events: AptosEvent[], + filter: TransactionFilter + ): Promise; } export interface MetadataRepository { diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index 40a09a511..0a526bab3 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -1,13 +1,35 @@ import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; -import { AptosEvent } from "../../../domain/entities/aptos"; +import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; import winston from "winston"; let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); -const SOURCE_EVENT_TAIL = "::state::WormholeMessage"; - export const aptosLogMessagePublishedMapper = ( - tx: AptosEvent[] + tx: TransactionsByVersion ): LogFoundEvent | undefined => { - return undefined; + if (!tx.blockTime) { + throw new Error(`Block time is missing for tx ${tx.hash}`); + } + + if (tx) { + logger.info( + `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: 22][sender: ${tx.sender}}][sequence: ${tx.sequence}]` + ); + + return { + name: "log-message-published", + address: tx.address!, + chainId: 22, + txHash: tx.hash!, + blockHeight: tx.blockHeight!, + blockTime: tx.blockTime, + attributes: { + sender: tx.sender!, + sequence: Number(tx.sequence!), + payload: tx.payload!, + nonce: Number(tx.nonce), + consistencyLevel: tx.consistencyLevel!, + }, + }; + } }; diff --git a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json index 1403f6d2f..c1d38c084 100644 --- a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json +++ b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json @@ -905,6 +905,25 @@ ] } ] + }, + { + "chain": "aptos", + "protocols": [ + { + "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + "type": "Token Bridge", + "methods": [ + { + "methodId": "complete_transfer", + "method": "Token Bridge Manual" + }, + { + "methodId": "complete_transfer_with_payload", + "method": "Token Bridge Automatic" + } + ] + } + ] } ] } diff --git a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts index b616e201a..c15f4455b 100644 --- a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts @@ -37,8 +37,11 @@ import { import { PollAptosTransactions, PollAptosTransactionsConfig, + PollAptosTransactionsConfigProps, } from "../../domain/actions/aptos/PollAptosTransactions"; import { HandleAptosTransactions } from "../../domain/actions/aptos/HandleAptosTransactions"; +import { aptosLogMessagePublishedMapper } from "../mappers/aptos/aptosLogMessagePublishedMapper"; +import { aptosRedeemedTransactionFoundMapper } from "../mappers/aptos/aptosRedeemedTransactionFoundMapper"; export class StaticJobRepository implements JobRepository { private fileRepo: FileMetadataRepository; @@ -156,7 +159,11 @@ export class StaticJobRepository implements JobRepository { const pollAptosTransactions = (jobDef: JobDefinition) => new PollAptosTransactions( - new PollAptosTransactionsConfig({ ...jobDef.source.config, id: jobDef.id }), + new PollAptosTransactionsConfig({ + ...(jobDef.source.config as PollAptosTransactionsConfigProps), + id: jobDef.id, + environment: this.environment, + }), this.statsRepo, this.metadataRepo, this.aptosRepo @@ -171,8 +178,10 @@ export class StaticJobRepository implements JobRepository { this.mappers.set("evmRedeemedTransactionFoundMapper", evmRedeemedTransactionFoundMapper); this.mappers.set("solanaLogMessagePublishedMapper", solanaLogMessagePublishedMapper); this.mappers.set("solanaTransferRedeemedMapper", solanaTransferRedeemedMapper); - this.mappers.set("suiRedeemedTransactionFoundMapper", suiRedeemedTransactionFoundMapper); this.mappers.set("suiLogMessagePublishedMapper", suiLogMessagePublishedMapper); + this.mappers.set("suiRedeemedTransactionFoundMapper", suiRedeemedTransactionFoundMapper); + this.mappers.set("aptosLogMessagePublishedMapper", aptosLogMessagePublishedMapper); + this.mappers.set("aptosRedeemedTransactionFoundMapper", aptosRedeemedTransactionFoundMapper); // Targets const snsTarget = () => this.snsRepo.asTarget(); diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 76e74d3f3..30cc5c922 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,13 +1,9 @@ +import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptosTransactions"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; import { coalesceChainId } from "@certusone/wormhole-sdk/lib/cjs/utils/consts"; import { AptosEvent } from "../../../domain/entities/aptos"; -import { Sequence } from "../../../domain/actions/aptos/PollAptosTransactions"; import winston from "winston"; -const CORE_BRIDGE_ADDRESS = "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"; -const EVENT_HANDLE = `${CORE_BRIDGE_ADDRESS}::state::WormholeMessageHandle`; -const FIELD_NAME = "event"; - export class AptosJsonRPCBlockRepository { private readonly logger: winston.Logger; @@ -15,15 +11,18 @@ export class AptosJsonRPCBlockRepository { this.logger = winston.child({ module: "AptossonRPCBlockRepository" }); } - async getSequenceNumber(range: Sequence | undefined): Promise { + async getSequenceNumber( + range: Sequence | undefined, + filter: TransactionFilter + ): Promise { try { const fromSequence = range?.fromSequence ? Number(range?.fromSequence) : undefined; const toSequence = range?.toSequence ? Number(range?.toSequence) : undefined; const results = await this.client.getEventsByEventHandle( - CORE_BRIDGE_ADDRESS, - EVENT_HANDLE, - FIELD_NAME, + filter.address, + filter.event, + filter.fieldName, fromSequence, toSequence ); @@ -34,22 +33,30 @@ export class AptosJsonRPCBlockRepository { } } - async getTransactionsForVersions(events: AptosEvent[]): Promise { + async getTransactionsForVersions( + events: AptosEvent[], + filter: TransactionFilter + ): Promise { const transactionsByVersion: TransactionsByVersion[] = []; for (const event of events) { - const result = await this.client.getTransactionByVersion(Number(event.version)); + const transaction = await this.client.getTransactionByVersion(Number(event.version)); + const block = await this.client.getBlockByVersion(Number(event.version)); + const tx = { consistencyLevel: event.data.consistency_level, - timestamp: result.timestamp, - sequence: result.sequence_number, - version: result.version, - payload: result.payload, - sender: result.sender, - status: result.success, - events: result.events, + blockHeight: block.block_height, + timestamp: transaction.timestamp, + blockTime: block.block_timestamp, + sequence: transaction.sequence_number, + version: transaction.version, + payload: transaction.payload, + address: filter.address, + sender: transaction.sender, + status: transaction.success, + events: transaction.events, nonce: event.data.nonce, - hash: result.hash, + hash: transaction.hash, }; transactionsByVersion.push(tx); } @@ -62,21 +69,20 @@ export class AptosJsonRPCBlockRepository { } } -type ResultBlocksHeight = { - sequence_number: bigint; -}; - export type TransactionsByVersion = { consistencyLevel?: number; + blockHeight?: bigint; timestamp?: string; + blockTime: number; sequence?: string; version?: string; payload?: string; + address?: string; sender?: string; - status: boolean; - events: any; + status?: boolean; + events?: any; nonce?: string; - hash: string; + hash?: string; }; const makeVaaKey = (transactionHash: string, emitter: string, seq: string): string => diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index 67cfe182f..bfb3eb0c4 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -1,9 +1,9 @@ +import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptosTransactions"; import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; import { TransactionsByVersion } from "./AptosJsonRPCBlockRepository"; import { AptosRepository } from "../../../domain/repositories"; import { AptosEvent } from "../../../domain/entities/aptos"; import { Options } from "../common/rateLimitedOptions"; -import { Sequence } from "../../../domain/actions/aptos/PollAptosTransactions"; import winston from "winston"; export class RateLimitedAptosJsonRPCBlockRepository @@ -15,11 +15,16 @@ export class RateLimitedAptosJsonRPCBlockRepository this.logger = winston.child({ module: "RateLimitedAptosJsonRPCBlockRepository" }); } - getSequenceNumber(range: Sequence | undefined): Promise { - return this.breaker.fn(() => this.delegate.getSequenceNumber(range)).execute(); + getSequenceNumber(range: Sequence | undefined, filter: TransactionFilter): Promise { + return this.breaker.fn(() => this.delegate.getSequenceNumber(range, filter)).execute(); } - getTransactionsForVersions(events: AptosEvent[]): Promise { - return this.breaker.fn(() => this.delegate.getTransactionsForVersions(events)).execute(); + getTransactionsForVersions( + events: AptosEvent[], + filter: TransactionFilter + ): Promise { + return this.breaker + .fn(() => this.delegate.getTransactionsForVersions(events, filter)) + .execute(); } } diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index f23c67a26..77b5b44f7 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -65,6 +65,15 @@ export class InstrumentedAptosProvider { throw e; } } + + public async getBlockByVersion(version: number): Promise { + try { + const result = await this.client.getBlockByVersion(version); + return result; + } catch (e) { + throw e; + } + } } export type HttpClientOptions = { From 7fa0d14b554d8ac09fad09280bbdcafe9193ac32 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 4 Mar 2024 16:40:13 -0300 Subject: [PATCH 04/30] Resolve issues and add new pod for target events --- blockchain-watcher/config/default.json | 2 +- .../actions/aptos/PollAptosTransactions.ts | 10 - .../aptosRedeemedTransactionFoundMapper.ts | 3 - .../mappers/contractsMapperConfig.json | 10 +- .../workers/source-events-1.yaml | 62 +++++ .../workers/target-events-1.yaml | 216 ++++++++++++++++++ 6 files changed, 282 insertions(+), 21 deletions(-) create mode 100644 deploy/blockchain-watcher/workers/target-events-1.yaml diff --git a/blockchain-watcher/config/default.json b/blockchain-watcher/config/default.json index 876ea98a8..e3f9735e2 100644 --- a/blockchain-watcher/config/default.json +++ b/blockchain-watcher/config/default.json @@ -135,7 +135,7 @@ "name": "aptos", "network": "testnet", "chainId": 22, - "rpcs": [""], + "rpcs": ["https://fullnode.devnet.aptoslabs.com/v1"], "timeout": 10000 }, "arbitrum": { diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts index aabfae065..84bb49964 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -2,16 +2,6 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repos import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; -/** - * Instead of fetching block per block and scanning for transactions, - * this poller uses the queryTransactions function from the sui sdk - * to set up a filter and retrieve all transactions that match it, - * thus avoiding the need to scan blocks which may not have any - * valuable information. - * - * Each queryTransactions request accepts a cursor parameter, which - * is the digest of the last transaction of the previous page. - */ export class PollAptosTransactions extends RunPollingJob { protected readonly logger: Logger; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index 450a008f1..b5adbf652 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -4,9 +4,6 @@ import winston from "winston"; let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFoundMapper" }); -const REDEEM_EVENT_TAIL = "::complete_transfer::TransferRedeemed"; -const APTOS_CHAIN = "aptos"; - export const aptosRedeemedTransactionFoundMapper = ( tx: AptosEvent[] ): TransactionFoundEvent | undefined => { diff --git a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json index c1d38c084..93a16a3f7 100644 --- a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json +++ b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json @@ -910,16 +910,12 @@ "chain": "aptos", "protocols": [ { - "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], "type": "Token Bridge", "methods": [ { - "methodId": "complete_transfer", - "method": "Token Bridge Manual" - }, - { - "methodId": "complete_transfer_with_payload", - "method": "Token Bridge Automatic" + "methodId": "complete_transfer::submit_vaa_and_register_entry", + "method": "Token Bridge" } ] } diff --git a/deploy/blockchain-watcher/workers/source-events-1.yaml b/deploy/blockchain-watcher/workers/source-events-1.yaml index d1453fc47..9f111c69b 100644 --- a/deploy/blockchain-watcher/workers/source-events-1.yaml +++ b/deploy/blockchain-watcher/workers/source-events-1.yaml @@ -335,6 +335,37 @@ data: } } ] + }, + { + "id": "poll-log-message-published-aptos", + "chain": "aptos", + "source": { + "action": "PollAptosTransactions", + "config": { + "blockBatchSize": 100, + "commitment": "finalized", + "interval": 5000, + "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + "chain": "aptos", + "chainId": 22, + "filter": { + "fieldName": "event", + "address": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle" + } + } + }, + "handlers": [ + { + "action": "HandleAptosTransactions", + "target": "dummy", + "mapper": "aptosLogMessagePublishedMapper", + "config": { + "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", + "metricName": "process_source_event" + } + } + ] } ] mainnet-jobs.json: |- @@ -548,6 +579,37 @@ data: } } ] + }, + { + "id": "poll-log-message-published-aptos", + "chain": "aptos", + "source": { + "action": "PollAptosTransactions", + "config": { + "blockBatchSize": 100, + "commitment": "finalized", + "interval": 5000, + "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + "chain": "aptos", + "chainId": 22, + "filter": { + "fieldName": "event", + "address": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle" + } + } + }, + "handlers": [ + { + "action": "HandleAptosTransactions", + "target": "dummy", + "mapper": "aptosLogMessagePublishedMapper", + "config": { + "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", + "metricName": "process_source_event" + } + } + ] } ] --- diff --git a/deploy/blockchain-watcher/workers/target-events-1.yaml b/deploy/blockchain-watcher/workers/target-events-1.yaml new file mode 100644 index 000000000..4a39d67b3 --- /dev/null +++ b/deploy/blockchain-watcher/workers/target-events-1.yaml @@ -0,0 +1,216 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .NAME }}-target-events-1 + namespace: {{ .NAMESPACE }} + labels: + app: {{ .NAME }}-target-events-1 +spec: + selector: + app: {{ .NAME }}-target-events-1 + ports: + - port: {{ .PORT }} + targetPort: {{ .PORT }} + name: {{ .NAME }}-target-events-1 + protocol: TCP +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: blockchain-watcher-target-events-1 + namespace: {{ .NAMESPACE }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Mi + storageClassName: gp2 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .NAME }}-target-events-1-jobs + namespace: {{ .NAMESPACE }} +data: + testnet-jobs.json: |- + [ + { + "id": "poll-log-message-published-aptos", + "chain": "aptos", + "source": { + "action": "PollAptosTransactions", + "config": { + "blockBatchSize": 100, + "commitment": "finalized", + "interval": 5000, + "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], + "chain": "aptos", + "chainId": 22, + "filter": { + "fieldName": "event", + "address": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", + "event": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" + } + } + }, + "handlers": [ + { + "action": "HandleAptosTransactions", + "target": "dummy", + "mapper": "aptosRedeemedTransactionFoundMapper", + "config": { + "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", + "metricName": "process_source_event" + } + } + ] + } + ] + mainnet-jobs.json: |- + [ + { + "id": "poll-log-message-published-aptos", + "chain": "aptos", + "source": { + "action": "PollAptosTransactions", + "config": { + "blockBatchSize": 100, + "commitment": "finalized", + "interval": 5000, + "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], + "chain": "aptos", + "chainId": 22, + "filter": { + "fieldName": "event", + "address": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", + "event": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" + } + } + }, + "handlers": [ + { + "action": "HandleAptosTransactions", + "target": "dummy", + "mapper": "aptosRedeemedTransactionFoundMapper", + "config": { + "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", + "metricName": "process_source_event" + } + } + ] + } + ] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .NAME }}-target-events-1 + namespace: {{ .NAMESPACE }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .NAME }}-target-events-1 + template: + metadata: + labels: + app: {{ .NAME }}-target-events-1 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .PORT }}" + spec: + restartPolicy: Always + terminationGracePeriodSeconds: 30 + serviceAccountName: event-watcher + containers: + - name: {{ .NAME }} + image: {{ .IMAGE_NAME }} + env: + - name: NODE_ENV + value: {{ .NODE_ENV }} + - name: PORT + value: "{{ .PORT }}" + - name: LOG_LEVEL + value: {{ .LOG_LEVEL }} + - name: BLOCKCHAIN_ENV + value: {{ .BLOCKCHAIN_ENV }} + - name: DRY_RUN_ENABLED + value: "{{ .DRY_RUN_ENABLED }}" + - name: SNS_TOPIC_ARN + value: {{ .SNS_TOPIC_ARN }} + - name: SNS_REGION + value: {{ .SNS_REGION }} + - name: JOBS_DIR + value: /home/node/app/jobs + {{ if .BASE_RPCS }} + - name: BASE_RPCS + value: '{{ .BASE_RPCS }}' + {{ end }} + {{ if .OPTIMISM_RPCS }} + - name: OPTIMISM_RPCS + value: '{{ .OPTIMISM_RPCS }}' + {{ end }} + {{ if .ARBITRUM_RPCS }} + - name: ARBITRUM_RPCS + value: '{{ .ARBITRUM_RPCS }}' + {{ end }} + {{ if .POLYGON_RPCS }} + - name: POLYGON_RPCS + value: '{{ .POLYGON_RPCS }}' + {{ end }} + {{ if .AVALANCHE_RPCS }} + - name: AVALANCHE_RPCS + value: '{{ .AVALANCHE_RPCS }}' + {{ end }} + {{ if .ETHEREUM_SEPOLIA_RPCS }} + - name: ETHEREUM_SEPOLIA_RPCS + value: '{{ .ETHEREUM_SEPOLIA_RPCS }}' + {{ end }} + {{ if .BASE_SEPOLIA_RPCS }} + - name: BASE_SEPOLIA_RPCS + value: '{{ .BASE_SEPOLIA_RPCS }}' + {{ end }} + {{ if .OPTIMISM_SEPOLIA_RPCS }} + - name: OPTIMISM_SEPOLIA_RPCS + value: '{{ .OPTIMISM_SEPOLIA_RPCS }}' + {{ end }} + {{ if .ARBITRUM_SEPOLIA_RPCS }} + - name: ARBITRUM_SEPOLIA_RPCS + value: '{{ .ARBITRUM_SEPOLIA_RPCS }}' + {{ end }} + {{ if .ETHEREUM_HOLESKY_RPCS }} + - name: ETHEREUM_HOLESKY_RPCS + value: '{{ .ETHEREUM_HOLESKY_RPCS }}' + {{ end }} + {{ if .BSC_RPCS }} + - name: BSC_RPCS + value: '{{ .BSC_RPCS }}' + {{ end }} + {{ if .CELO_RPCS }} + - name: CELO_RPCS + value: '{{ .CELO_RPCS }}' + {{ end }} + resources: + limits: + memory: {{ .RESOURCES_LIMITS_MEMORY }} + cpu: {{ .RESOURCES_LIMITS_CPU }} + requests: + memory: {{ .RESOURCES_REQUESTS_MEMORY }} + cpu: {{ .RESOURCES_REQUESTS_CPU }} + volumeMounts: + - name: metadata-volume + mountPath: /home/node/app/metadata-repo + - name: jobs-volume + mountPath: /home/node/app/jobs + volumes: + - name: metadata-volume + persistentVolumeClaim: + claimName: blockchain-watcher-target-events-1 + - name: jobs-volume + configMap: + name: {{ .NAME }}-target-events-1-jobs + items: + - key: {{ .BLOCKCHAIN_ENV }}-jobs.json + path: jobs.json From 23d5c81604584847e1fad6862f3fa085394a5574 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 4 Mar 2024 17:40:26 -0300 Subject: [PATCH 05/30] Resolve test and improve code --- .../actions/aptos/PollAptosTransactions.ts | 18 +++---- .../aptos/aptosLogMessagePublishedMapper.ts | 6 ++- .../aptos/AptosJsonRPCBlockRepository.ts | 50 +++++++++++-------- .../rpc/http/InstrumentedAptosProvider.ts | 8 --- .../repositories/RepositoriesBuilder.test.ts | 3 ++ .../repositories/StaticJobRepository.test.ts | 3 ++ blockchain-watcher/test/mocks/configMock.ts | 9 +++- .../workers/source-events-1.yaml | 14 +++++- 8 files changed, 67 insertions(+), 44 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts index 84bb49964..c3e5867cc 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -63,6 +63,14 @@ export class PollAptosTransactions extends RunPollingJob { } private getBlockRange(): Sequence | undefined { + // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size + if (this.cfg.fromSequence) { + return { + fromSequence: Number(this.lastSequence), + toSequence: this.cfg.getBlockBatchSize(), + }; + } + if (this.previousSequence && this.lastSequence) { // if process the [same sequence], return the same last sequence and the to sequence equal 1 if (this.previousSequence === this.lastSequence) { @@ -91,14 +99,6 @@ export class PollAptosTransactions extends RunPollingJob { }; } } - - // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size - if (this.cfg.fromSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: this.cfg.getBlockBatchSize(), - }; - } } protected async persist(): Promise { @@ -122,7 +122,7 @@ export class PollAptosTransactions extends RunPollingJob { ...labels, type: "max", }); - this.statsRepo.measure("polling_cursor", this.lastSequence ?? 0n, { + this.statsRepo.measure("polling_cursor", this.sequenceHeightCursor ?? 0n, { ...labels, type: "current", }); diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index 0a526bab3..5af5b97d7 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -2,6 +2,8 @@ import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; import winston from "winston"; +const CHAIN_ID_APTOS = 22; + let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); export const aptosLogMessagePublishedMapper = ( @@ -13,13 +15,13 @@ export const aptosLogMessagePublishedMapper = ( if (tx) { logger.info( - `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: 22][sender: ${tx.sender}}][sequence: ${tx.sequence}]` + `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${tx.sender}}][sequence: ${tx.sequence}]` ); return { name: "log-message-published", address: tx.address!, - chainId: 22, + chainId: CHAIN_ID_APTOS, txHash: tx.hash!, blockHeight: tx.blockHeight!, blockTime: tx.blockTime, diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 30cc5c922..fce9fa901 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -37,31 +37,36 @@ export class AptosJsonRPCBlockRepository { events: AptosEvent[], filter: TransactionFilter ): Promise { - const transactionsByVersion: TransactionsByVersion[] = []; + try { + const transactionsByVersion: TransactionsByVersion[] = []; - for (const event of events) { - const transaction = await this.client.getTransactionByVersion(Number(event.version)); - const block = await this.client.getBlockByVersion(Number(event.version)); + for (const event of events) { + const transaction = await this.client.getTransactionByVersion(Number(event.version)); + const block = await this.client.getBlockByVersion(Number(event.version)); - const tx = { - consistencyLevel: event.data.consistency_level, - blockHeight: block.block_height, - timestamp: transaction.timestamp, - blockTime: block.block_timestamp, - sequence: transaction.sequence_number, - version: transaction.version, - payload: transaction.payload, - address: filter.address, - sender: transaction.sender, - status: transaction.success, - events: transaction.events, - nonce: event.data.nonce, - hash: transaction.hash, - }; - transactionsByVersion.push(tx); - } + const tx = { + consistencyLevel: event.data.consistency_level, + blockHeight: block.block_height, + timestamp: transaction.timestamp, + blockTime: block.block_timestamp, + sequence: transaction.sequence_number, + version: transaction.version, + payload: transaction.payload, + address: filter.address, + sender: transaction.sender, + status: transaction.success, + events: transaction.events, + nonce: event.data.nonce, + hash: transaction.hash, + }; + transactionsByVersion.push(tx); + } - return transactionsByVersion; + return transactionsByVersion; + } catch (e) { + this.handleError(e, "getSequenceNumber"); + throw e; + } } private handleError(e: any, method: string) { @@ -85,5 +90,6 @@ export type TransactionsByVersion = { hash?: string; }; +// TODO: Remove const makeVaaKey = (transactionHash: string, emitter: string, seq: string): string => `${transactionHash}:${coalesceChainId("aptos")}/${emitter}/${seq}`; diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index 77b5b44f7..fb8eceeef 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -1,20 +1,15 @@ import { AptosClient } from "aptos"; import winston from "winston"; -// make url and chain required type InstrumentedAptosProviderOptions = Required> & HttpClientOptions; -/** - * A simple HTTP client with exponential backoff retries and 429 handling. - */ export class InstrumentedAptosProvider { private initialDelay: number = 1_000; private maxDelay: number = 60_000; private retries: number = 0; private timeout: number = 5_000; private url: string; - private chain: string; client: AptosClient; private logger: winston.Logger = winston.child({ module: "InstrumentedAptosProvider" }); @@ -28,9 +23,6 @@ export class InstrumentedAptosProvider { if (!options.url) throw new Error("URL is required"); this.url = options.url; - if (!options.chain) throw new Error("Chain is required"); - this.chain = options.chain; - this.client = new AptosClient(this.url); } diff --git a/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts index 1e9a2e2eb..b2d790c3a 100644 --- a/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts @@ -12,6 +12,7 @@ import { RateLimitedSolanaSlotRepository, SnsEventRepository, } from "../../../src/infrastructure/repositories"; +import { RateLimitedAptosJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository"; describe("RepositoriesBuilder", () => { it("should be throw error because dose not have any chain", async () => { @@ -96,10 +97,12 @@ describe("RepositoriesBuilder", () => { expect(repos.getEvmBlockRepository("ethereum-holesky")).toBeInstanceOf( RateLimitedEvmJsonRPCBlockRepository ); + expect(repos.getAptosRepository()).toBeInstanceOf(RateLimitedAptosJsonRPCBlockRepository); expect(repos.getMetadataRepository()).toBeInstanceOf(FileMetadataRepository); expect(repos.getSnsEventRepository()).toBeInstanceOf(SnsEventRepository); expect(repos.getStatsRepository()).toBeInstanceOf(PromStatRepository); expect(repos.getSolanaSlotRepository()).toBeInstanceOf(RateLimitedSolanaSlotRepository); expect(repos.getSuiRepository()).toBeInstanceOf(RateLimitedSuiJsonRPCBlockRepository); + expect(repos.getAptosRepository()).toBeInstanceOf(RateLimitedAptosJsonRPCBlockRepository); }); }); diff --git a/blockchain-watcher/test/infrastructure/repositories/StaticJobRepository.test.ts b/blockchain-watcher/test/infrastructure/repositories/StaticJobRepository.test.ts index 38c4fb3c4..0269c19ce 100644 --- a/blockchain-watcher/test/infrastructure/repositories/StaticJobRepository.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/StaticJobRepository.test.ts @@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import fs from "fs"; import { SnsEventRepository, StaticJobRepository } from "../../../src/infrastructure/repositories"; import { + AptosRepository, EvmBlockRepository, MetadataRepository, SolanaSlotRepository, @@ -19,6 +20,7 @@ const statsRepo = {} as any as StatRepository; const snsRepo = {} as any as SnsEventRepository; const solanaSlotRepo = {} as any as SolanaSlotRepository; const suiRepo = {} as any as SuiRepository; +const aptosRepo = {} as any as AptosRepository; let repo: StaticJobRepository; @@ -33,6 +35,7 @@ describe("StaticJobRepository", () => { snsRepo, solanaSlotRepo, suiRepo, + aptosRepo, }); }); diff --git a/blockchain-watcher/test/mocks/configMock.ts b/blockchain-watcher/test/mocks/configMock.ts index ba1849425..832157b44 100644 --- a/blockchain-watcher/test/mocks/configMock.ts +++ b/blockchain-watcher/test/mocks/configMock.ts @@ -80,6 +80,13 @@ export const configMock = (): Config => { rpcs: ["http://localhost"], timeout: 10000, }, + aptos: { + name: "aptos", + network: "testnet", + chainId: 22, + rpcs: ["http://localhost"], + timeout: 10000, + }, arbitrum: { name: "arbitrum", network: "goerli", @@ -170,7 +177,7 @@ export const configMock = (): Config => { dir: "./metadata-repo/jobs", }, chains: chainsRecord, - enabledPlatforms: ["solana", "evm", "sui"], + enabledPlatforms: ["solana", "evm", "sui", "aptos"], }; return cfg; diff --git a/deploy/blockchain-watcher/workers/source-events-1.yaml b/deploy/blockchain-watcher/workers/source-events-1.yaml index 9f111c69b..3fee49b9c 100644 --- a/deploy/blockchain-watcher/workers/source-events-1.yaml +++ b/deploy/blockchain-watcher/workers/source-events-1.yaml @@ -362,7 +362,12 @@ data: "mapper": "aptosLogMessagePublishedMapper", "config": { "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", - "metricName": "process_source_event" + "metricName": "process_source_event", + "metricLabels": { + "job": "poll-log-message-published-aptos", + "chain": "aptos", + "commitment": "immediate" + } } } ] @@ -606,7 +611,12 @@ data: "mapper": "aptosLogMessagePublishedMapper", "config": { "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", - "metricName": "process_source_event" + "metricName": "process_source_event", + "metricLabels": { + "job": "poll-log-message-published-aptos", + "chain": "aptos", + "commitment": "immediate" + } } } ] From 0a28bc394f7048e28a4611b8313e4103b7a985f4 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Tue, 5 Mar 2024 11:52:29 -0300 Subject: [PATCH 06/30] Add test --- .../actions/aptos/PollAptosTransactions.ts | 67 +++- .../aptos/aptosLogMessagePublishedMapper.ts | 40 ++- .../aptos/HandleAptosTransactions.test.ts | 172 ++++++++++ .../aptos/PollAptosTransactions.test.ts | 319 ++++++++++++++++++ .../aptosLogMessagePublishedMapper.test.ts | 102 ++++++ ...s => evmLogMessagePublishedMapper.test.ts} | 0 6 files changed, 664 insertions(+), 36 deletions(-) create mode 100644 blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts create mode 100644 blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts create mode 100644 blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts rename blockchain-watcher/test/infrastructure/mappers/evm/{evmLogMessagePublished.test.ts => evmLogMessagePublishedMapper.test.ts} (100%) diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts index c3e5867cc..ff287c84c 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts @@ -1,4 +1,5 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; +import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; @@ -44,29 +45,43 @@ export class PollAptosTransactions extends RunPollingJob { } protected async get(): Promise { + let populatedTransactions: TransactionsByVersion[] = []; + const filter = this.cfg.filter; const range = this.getBlockRange(); - const events = await this.repo.getSequenceNumber(range, filter); + const batches = this.createBatches(range); - // save previous sequence with last sequence and update last sequence with the new sequence - this.previousSequence = this.lastSequence; - this.lastSequence = BigInt(events[events.length - 1].sequence_number); + for (const batch of batches) { + const events = await this.repo.getSequenceNumber( + { + fromSequence: range?.fromSequence, + toSequence: batch, + }, + filter + ); - if (this.previousSequence && this.lastSequence && this.previousSequence === this.lastSequence) { - return []; - } + // save previous sequence with last sequence and update last sequence with the new sequence + this.previousSequence = this.lastSequence; + this.lastSequence = BigInt(events[events.length - 1].sequence_number); - const transactions = await this.repo.getTransactionsForVersions(events, filter); + const transactions = await this.repo.getTransactionsForVersions(events, filter); + transactions.forEach((tx) => { + populatedTransactions.push(tx); + }); + } - return transactions; + this.logger.info( + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${this.cfg.addresses}][blocks:${range?.fromSequence} - ${range?.toSequence}]` + ); + return populatedTransactions; } private getBlockRange(): Sequence | undefined { // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size if (this.cfg.fromSequence) { return { - fromSequence: Number(this.lastSequence), + fromSequence: Number(this.cfg.fromSequence), toSequence: this.cfg.getBlockBatchSize(), }; } @@ -94,8 +109,7 @@ export class PollAptosTransactions extends RunPollingJob { if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.lastSequence) { return { fromSequence: Number(this.lastSequence), - toSequence: - Number(this.lastSequence) + this.cfg.getBlockBatchSize() - Number(this.lastSequence), + toSequence: this.cfg.getBlockBatchSize(), }; } } @@ -127,6 +141,25 @@ export class PollAptosTransactions extends RunPollingJob { type: "current", }); } + + private createBatches(range: Sequence | undefined): number[] { + let batchSize = 100; + let total = 1; + + if (range && range.toSequence) { + batchSize = range.toSequence < batchSize ? range.toSequence : batchSize; + total = range.toSequence ?? total; + } + + const numBatches = Math.ceil(total / batchSize); + const batches: number[] = []; + + for (let i = 0; i < numBatches; i++) { + batches.push(batchSize); + } + + return batches; + } } export class PollAptosTransactionsConfig { @@ -159,6 +192,10 @@ export class PollAptosTransactionsConfig { public get filter(): TransactionFilter { return this.props.filter; } + + public get addresses(): string[] { + return this.props.addresses; + } } export interface PollAptosTransactionsConfigProps { @@ -176,7 +213,7 @@ export interface PollAptosTransactionsConfigProps { id: string; } -type PollAptosTransactionsMetadata = { +export type PollAptosTransactionsMetadata = { previousSequence?: bigint; lastSequence?: bigint; }; @@ -188,6 +225,6 @@ export type TransactionFilter = { }; export type Sequence = { - fromSequence: number; - toSequence: number; + fromSequence?: number; + toSequence?: number; }; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index 5af5b97d7..f3eb7295d 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -10,28 +10,26 @@ export const aptosLogMessagePublishedMapper = ( tx: TransactionsByVersion ): LogFoundEvent | undefined => { if (!tx.blockTime) { - throw new Error(`Block time is missing for tx ${tx.hash}`); + throw new Error(`[aptos] Block time is missing for tx ${tx.hash}`); } - if (tx) { - logger.info( - `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${tx.sender}}][sequence: ${tx.sequence}]` - ); + logger.info( + `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${tx.sender}}][sequence: ${tx.sequence}]` + ); - return { - name: "log-message-published", - address: tx.address!, - chainId: CHAIN_ID_APTOS, - txHash: tx.hash!, - blockHeight: tx.blockHeight!, - blockTime: tx.blockTime, - attributes: { - sender: tx.sender!, - sequence: Number(tx.sequence!), - payload: tx.payload!, - nonce: Number(tx.nonce), - consistencyLevel: tx.consistencyLevel!, - }, - }; - } + return { + name: "log-message-published", + address: tx.address!, + chainId: CHAIN_ID_APTOS, + txHash: tx.hash!, + blockHeight: tx.blockHeight!, + blockTime: tx.blockTime, + attributes: { + sender: tx.sender!, + sequence: Number(tx.sequence!), + payload: tx.payload!, + nonce: Number(tx.nonce), + consistencyLevel: tx.consistencyLevel!, + }, + }; }; diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts new file mode 100644 index 000000000..606c5ca99 --- /dev/null +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -0,0 +1,172 @@ +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { LogFoundEvent, LogMessagePublished } from "../../../../src/domain/entities"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { StatRepository } from "../../../../src/domain/repositories"; +import { + HandleAptosTransactions, + HandleAptosTransactionsOptions, +} from "../../../../src/domain/actions/aptos/HandleAptosTransactions"; + +let targetRepoSpy: jest.SpiedFunction<(typeof targetRepo)["save"]>; +let statsRepo: StatRepository; + +let handleAptosTransactions: HandleAptosTransactions; +let txs: TransactionsByVersion[]; +let cfg: HandleAptosTransactionsOptions; + +describe("HandleAptosTransactions", () => { + afterEach(async () => {}); + + it("should be able to map source events tx", async () => { + givenConfig(); + givenStatsRepository(); + givenHandleEvmLogs(); + + const result = await handleAptosTransactions.handle(txs); + + expect(result).toHaveLength(1); + expect(result[0].name).toBe("log-message-published"); + expect(result[0].chainId).toBe(22); + expect(result[0].txHash).toBe( + "0x99f9cd1ea181d568ba4d89e414dcf1b129968b1c805388f29821599a447b7741" + ); + expect(result[0].address).toBe( + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" + ); + }); +}); + +const mapper = (tx: TransactionsByVersion) => { + return { + name: "log-message-published", + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + chainId: 22, + txHash: "0x99f9cd1ea181d568ba4d89e414dcf1b129968b1c805388f29821599a447b7741", + blockHeight: 153549311n, + blockTime: 1709645685704036, + attributes: { + sender: "0xa216910c9a74291aa3e26135486399b7a04771977687c3da57a7498e77103658", + sequence: 203, + payload: + "0x7b2274797065223a226d657373616765222c226d657373616765223a2268656c6c6f222c227369676e6174757265223a226d657373616765222c2276616c7565223a7b226d657373616765223a2268656c6c6f222c2274797065223a226d657373616765222c227369676e6174757265223a226d657373616765227d7d", + nonce: 75952, + consistencyLevel: 0, + protocol: "Token Bridge", + }, + }; +}; + +const targetRepo = { + save: async (events: LogFoundEvent>[]) => { + Promise.resolve(); + }, + failingSave: async (events: LogFoundEvent>[]) => { + Promise.reject(); + }, +}; + +const givenHandleEvmLogs = (targetFn: "save" | "failingSave" = "save") => { + targetRepoSpy = jest.spyOn(targetRepo, targetFn); + handleAptosTransactions = new HandleAptosTransactions( + cfg, + mapper, + () => Promise.resolve(), + statsRepo + ); +}; + +const givenConfig = () => { + cfg = { + id: "poll-log-message-published-aptos", + metricName: "process_source_event", + metricLabels: { + job: "poll-log-message-published-aptos", + chain: "aptos", + commitment: "immediate", + }, + }; +}; + +const givenStatsRepository = () => { + statsRepo = { + count: () => {}, + measure: () => {}, + report: () => Promise.resolve(""), + }; +}; + +txs = [ + { + consistencyLevel: 0, + blockHeight: 153517771n, + timestamp: "1709638693443328", + blockTime: 1709638693443328, + sequence: "34", + version: "482649547", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + status: true, + events: [ + { + guid: { + creation_number: "11", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "0", + type: "0x1::coin::WithdrawEvent", + data: { amount: "9950053" }, + }, + { + guid: { + creation_number: "3", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "16", + type: "0x1::coin::WithdrawEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "4", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149041", + type: "0x1::coin::DepositEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "2", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149040", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", + data: { + consistency_level: 0, + nonce: "76704", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + sender: "1", + sequence: "146094", + timestamp: "1709638693", + }, + }, + { + guid: { creation_number: "0", account_address: "0x0" }, + sequence_number: "0", + type: "0x1::transaction_fee::FeeStatement", + data: { + execution_gas_units: "7", + io_gas_units: "12", + storage_fee_octas: "0", + storage_fee_refund_octas: "0", + total_charge_gas_units: "19", + }, + }, + ], + nonce: "76704", + hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", + }, +]; diff --git a/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts new file mode 100644 index 000000000..8990d9540 --- /dev/null +++ b/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts @@ -0,0 +1,319 @@ +import { + PollAptosTransactions, + PollAptosTransactionsConfig, + PollAptosTransactionsMetadata, +} from "../../../../src/domain/actions/aptos/PollAptosTransactions"; +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { + AptosRepository, + MetadataRepository, + StatRepository, +} from "../../../../src/domain/repositories"; +import { thenWaitForAssertion } from "../../../wait-assertion"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; + +let getTransactionsForVersionsSpy: jest.SpiedFunction< + AptosRepository["getTransactionsForVersions"] +>; +let getSequenceNumberSpy: jest.SpiedFunction; +let metadataSaveSpy: jest.SpiedFunction["save"]>; + +let handlerSpy: jest.SpiedFunction<(txs: TransactionsByVersion[]) => Promise>; + +let metadataRepo: MetadataRepository; +let aptosRepo: AptosRepository; +let statsRepo: StatRepository; + +let handlers = { + working: (txs: TransactionsByVersion[]) => Promise.resolve(), + failing: (txs: TransactionsByVersion[]) => Promise.reject(), +}; +let pollAptosTransactions: PollAptosTransactions; + +let props = { + blockBatchSize: 100, + fromSequence: 0n, + toSequence: 0n, + environment: "testnet", + commitment: "finalized", + addresses: ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + interval: 5000, + topics: [], + chainId: 22, + filter: { + fieldName: "event", + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + }, + chain: "aptos", + id: "poll-log-message-published-aptos", +}; + +let cfg = new PollAptosTransactionsConfig(props); + +describe("pollAptosTransactions", () => { + afterEach(async () => { + await pollAptosTransactions.stop(); + }); + + it("should be not generate range (from and to sequence) and search the latest sequence plus block batch size cfg", async () => { + givenEvmBlockRepository(); + givenMetadataRepository(); + givenStatsRepository(); + givenPollAptosTx(cfg); + + await whenPollEvmLogsStarts(); + + await thenWaitForAssertion( + () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), + () => + expect(getSequenceNumberSpy).toBeCalledWith( + { fromSequence: undefined, toSequence: 100 }, + { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + fieldName: "event", + } + ), + () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + ); + }); + + it("should be use fromSequence and batch size cfg from cfg", async () => { + givenEvmBlockRepository(); + givenMetadataRepository(); + givenStatsRepository(); + // Se fromSequence for cfg + props.fromSequence = 146040n; + givenPollAptosTx(cfg); + + await whenPollEvmLogsStarts(); + + await thenWaitForAssertion( + () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), + () => + expect(getSequenceNumberSpy).toBeCalledWith( + { fromSequence: 146040, toSequence: 100 }, + { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + fieldName: "event", + } + ), + () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + ); + }); + + it("should be return the same last sequence and the to sequence equal 1", async () => { + givenEvmBlockRepository(); + givenMetadataRepository({ previousSequence: 146040n, lastSequence: 146040n }); + givenStatsRepository(); + // Se fromSequence for cfg + props.fromSequence = 0n; + givenPollAptosTx(cfg); + + await whenPollEvmLogsStarts(); + + await thenWaitForAssertion( + () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), + () => + expect(getSequenceNumberSpy).toBeCalledWith( + { fromSequence: 146040, toSequence: 1 }, + { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + fieldName: "event", + } + ), + () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + ); + }); + + it("should be return the difference between the last sequence and the previous sequence plus 1", async () => { + givenEvmBlockRepository(); + givenMetadataRepository({ previousSequence: 146000n, lastSequence: 146040n }); + givenStatsRepository(); + // Se fromSequence for cfg + props.fromSequence = 0n; + givenPollAptosTx(cfg); + + await whenPollEvmLogsStarts(); + + await thenWaitForAssertion( + () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), + () => + expect(getSequenceNumberSpy).toBeCalledWith( + { fromSequence: 146040, toSequence: 41 }, + { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + fieldName: "event", + } + ), + () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + ); + }); + + it("should be if return the last sequence and the to sequence equal the block batch size", async () => { + givenEvmBlockRepository(); + givenMetadataRepository({ previousSequence: undefined, lastSequence: 146040n }); + givenStatsRepository(); + // Se fromSequence for cfg + props.fromSequence = 0n; + givenPollAptosTx(cfg); + + await whenPollEvmLogsStarts(); + + await thenWaitForAssertion( + () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), + () => + expect(getSequenceNumberSpy).toBeCalledWith( + { fromSequence: 146040, toSequence: 100 }, + { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + event: + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + fieldName: "event", + } + ), + () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + ); + }); +}); + +const givenEvmBlockRepository = () => { + const events = [ + { + version: "481740133", + guid: { + creation_number: "2", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "148985", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", + data: { + consistency_level: 0, + nonce: "41611", + payload: + "0x0100000000000000000000000000000000000000000000000000000000003b826000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10017000000000000000000000000451febd0f01b9d6bda1a5b9d0b6ef88026e4a79100170000000000000000000000000000000000000000000000000000000000011c1e", + sender: "1", + sequence: "146040", + timestamp: "1709585379", + }, + }, + ]; + + const txs = [ + { + consistencyLevel: 0, + blockHeight: 153517771n, + timestamp: "1709638693443328", + blockTime: 1709638693443328, + sequence: "34", + version: "482649547", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + status: true, + events: [ + { + guid: { + creation_number: "11", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "0", + type: "0x1::coin::WithdrawEvent", + data: { amount: "9950053" }, + }, + { + guid: { + creation_number: "3", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "16", + type: "0x1::coin::WithdrawEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "4", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149041", + type: "0x1::coin::DepositEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "2", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149040", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", + data: { + consistency_level: 0, + nonce: "76704", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + sender: "1", + sequence: "146094", + timestamp: "1709638693", + }, + }, + { + guid: { creation_number: "0", account_address: "0x0" }, + sequence_number: "0", + type: "0x1::transaction_fee::FeeStatement", + data: { + execution_gas_units: "7", + io_gas_units: "12", + storage_fee_octas: "0", + storage_fee_refund_octas: "0", + total_charge_gas_units: "19", + }, + }, + ], + nonce: "76704", + hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", + }, + ]; + + aptosRepo = { + getSequenceNumber: () => Promise.resolve(events), + getTransactionsForVersions: () => Promise.resolve(txs), + }; + + getSequenceNumberSpy = jest.spyOn(aptosRepo, "getSequenceNumber"); + getTransactionsForVersionsSpy = jest.spyOn(aptosRepo, "getTransactionsForVersions"); + handlerSpy = jest.spyOn(handlers, "working"); +}; + +const givenMetadataRepository = (data?: PollAptosTransactionsMetadata) => { + metadataRepo = { + get: () => Promise.resolve(data), + save: () => Promise.resolve(), + }; + metadataSaveSpy = jest.spyOn(metadataRepo, "save"); +}; + +const givenStatsRepository = () => { + statsRepo = { + count: () => {}, + measure: () => {}, + report: () => Promise.resolve(""), + }; +}; + +const givenPollAptosTx = (cfg: PollAptosTransactionsConfig) => { + pollAptosTransactions = new PollAptosTransactions(cfg, statsRepo, metadataRepo, aptosRepo); +}; + +const whenPollEvmLogsStarts = async () => { + pollAptosTransactions.run([handlers.working]); +}; diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts new file mode 100644 index 000000000..d95cd99ee --- /dev/null +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from "@jest/globals"; +import { aptosLogMessagePublishedMapper } from "../../../../src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; + +describe("aptosLogMessagePublishedMapper", () => { + it("should be able to map log to aptosLogMessagePublishedMapper", async () => { + const result = aptosLogMessagePublishedMapper(txs); + if (result) { + expect(result.name).toBe("log-message-published"); + expect(result.chainId).toBe(22); + expect(result.txHash).toBe( + "0x99f9cd1ea181d568ba4d89e414dcf1b129968b1c805388f29821599a447b7741" + ); + expect(result.address).toBe( + "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" + ); + expect(result.attributes.consistencyLevel).toBe(0); + expect(result.attributes.nonce).toBe(76704); + expect(result.attributes.payload).toBe( + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0" + ); + expect(result.attributes.sender).toBe( + "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2" + ); + expect(result.attributes.sequence).toBe(34); + } + }); +}); + +const txs: TransactionsByVersion = { + consistencyLevel: 0, + blockHeight: 153517771n, + timestamp: "1709638693443328", + blockTime: 1709638693443328, + sequence: "34", + version: "482649547", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + status: true, + events: [ + { + guid: { + creation_number: "11", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "0", + type: "0x1::coin::WithdrawEvent", + data: { amount: "9950053" }, + }, + { + guid: { + creation_number: "3", + account_address: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", + }, + sequence_number: "16", + type: "0x1::coin::WithdrawEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "4", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149041", + type: "0x1::coin::DepositEvent", + data: { amount: "0" }, + }, + { + guid: { + creation_number: "2", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "149040", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", + data: { + consistency_level: 0, + nonce: "76704", + payload: + "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", + sender: "1", + sequence: "146094", + timestamp: "1709638693", + }, + }, + { + guid: { creation_number: "0", account_address: "0x0" }, + sequence_number: "0", + type: "0x1::transaction_fee::FeeStatement", + data: { + execution_gas_units: "7", + io_gas_units: "12", + storage_fee_octas: "0", + storage_fee_refund_octas: "0", + total_charge_gas_units: "19", + }, + }, + ], + nonce: "76704", + hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", +}; diff --git a/blockchain-watcher/test/infrastructure/mappers/evm/evmLogMessagePublished.test.ts b/blockchain-watcher/test/infrastructure/mappers/evm/evmLogMessagePublishedMapper.test.ts similarity index 100% rename from blockchain-watcher/test/infrastructure/mappers/evm/evmLogMessagePublished.test.ts rename to blockchain-watcher/test/infrastructure/mappers/evm/evmLogMessagePublishedMapper.test.ts From 8c7f2ce4a4244e0f663f8b06bdac8b5faa5a7340 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Tue, 5 Mar 2024 12:00:11 -0300 Subject: [PATCH 07/30] Resolve test --- .../mappers/aptos/aptosLogMessagePublishedMapper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index d95cd99ee..d164615f9 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -9,7 +9,7 @@ describe("aptosLogMessagePublishedMapper", () => { expect(result.name).toBe("log-message-published"); expect(result.chainId).toBe(22); expect(result.txHash).toBe( - "0x99f9cd1ea181d568ba4d89e414dcf1b129968b1c805388f29821599a447b7741" + "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e" ); expect(result.address).toBe( "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" From dd18b5da98499155bb6f05c139fed8d23bfca8de Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 6 Mar 2024 19:09:38 -0300 Subject: [PATCH 08/30] Improve source event implementation --- .../domain/actions/aptos/GetAptosSequences.ts | 129 ++++++++++++++++++ ...{PollAptosTransactions.ts => PollAptos.ts} | 123 +++++------------ blockchain-watcher/src/domain/repositories.ts | 3 +- .../aptos/aptosLogMessagePublishedMapper.ts | 16 +-- .../repositories/StaticJobRepository.ts | 13 +- .../aptos/AptosJsonRPCBlockRepository.ts | 52 ++++--- .../RateLimitedAptosJsonRPCBlockRepository.ts | 6 +- .../rpc/http/InstrumentedAptosProvider.ts | 10 ++ .../aptos/HandleAptosTransactions.test.ts | 4 +- .../aptos/PollAptosTransactions.test.ts | 27 ++-- .../aptosLogMessagePublishedMapper.test.ts | 4 +- .../workers/source-events-1.yaml | 16 ++- .../workers/source-events.yaml | 2 +- .../workers/target-events-1.yaml | 4 +- 14 files changed, 262 insertions(+), 147 deletions(-) create mode 100644 blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts rename blockchain-watcher/src/domain/actions/aptos/{PollAptosTransactions.ts => PollAptos.ts} (52%) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts new file mode 100644 index 000000000..0becc5509 --- /dev/null +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -0,0 +1,129 @@ +import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosRepository } from "../../repositories"; +import winston from "winston"; +import { Filter, Sequence } from "./PollAptos"; + +export class GetAptosSequences { + private readonly repo: AptosRepository; + protected readonly logger: winston.Logger; + + private lastSequence?: bigint; + private previousSequence?: bigint; + + constructor(repo: AptosRepository) { + this.logger = winston.child({ module: "GetAptosSequences" }); + this.repo = repo; + } + + async execute(range: Sequence | undefined, opts: GetAptosOpts): Promise { + let populatedTransactions: TransactionsByVersion[] = []; + + const batches = this.createBatches(range); + + for (const batch of batches) { + const events = await this.repo.getSequenceNumber( + { + fromSequence: range?.fromSequence, + toSequence: batch, + }, + opts.filter + ); + + // update last sequence with the new sequence + this.lastSequence = BigInt(events[events.length - 1].sequence_number); + + if (opts.previousSequence == this.lastSequence) { + return []; + } + + // save previous sequence with last sequence + this.previousSequence = opts.lastSequence; + + const transactions = await this.repo.getTransactionsForVersions(events, opts.filter); + transactions.forEach((tx) => { + populatedTransactions.push(tx); + }); + } + + this.logger.info( + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][sequence: ${range?.fromSequence}]` + ); + return populatedTransactions; + } + + getBlockRange( + cfgBlockBarchSize: number, + cfgFromSequence: bigint | undefined, + savedPreviousSequence: bigint | undefined, + savedLastSequence: bigint | undefined + ): Sequence | undefined { + // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size + if (cfgFromSequence) { + return { + fromSequence: Number(cfgFromSequence), + toSequence: cfgBlockBarchSize, + }; + } + + if (savedPreviousSequence && savedLastSequence) { + // if process the [same sequence], return the same last sequence and the to sequence equal the block batch size + if (savedPreviousSequence === savedLastSequence) { + return { + fromSequence: Number(savedLastSequence), + toSequence: cfgBlockBarchSize, + }; + } + + // if process [different sequences], return the difference between the last sequence and the previous sequence plus 1 + if (savedPreviousSequence !== savedLastSequence) { + return { + fromSequence: Number(savedLastSequence), + toSequence: Number(savedLastSequence) - Number(savedPreviousSequence) + 1, + }; + } + } + + if (savedLastSequence) { + // if there is [no previous sequence], return the last sequence and the to sequence equal the block batch size + if (!cfgFromSequence || BigInt(cfgFromSequence) < savedLastSequence) { + return { + fromSequence: Number(savedLastSequence), + toSequence: cfgBlockBarchSize, + }; + } + } + } + + updatedRange() { + return { + previousSequence: this.previousSequence, + lastSequence: this.lastSequence, + }; + } + + private createBatches(range: Sequence | undefined): number[] { + let batchSize = 100; + let total = 1; + + if (range && range.toSequence) { + batchSize = range.toSequence < batchSize ? range.toSequence : batchSize; + total = range.toSequence ?? total; + } + + const numBatches = Math.ceil(total / batchSize); + const batches: number[] = []; + + for (let i = 0; i < numBatches; i++) { + batches.push(batchSize); + } + + return batches; + } +} + +export type GetAptosOpts = { + addresses: string[]; + filter: Filter; + previousSequence?: bigint | undefined; + lastSequence?: bigint | undefined; +}; diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts similarity index 52% rename from blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts rename to blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index ff287c84c..4d7e5aa88 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -1,23 +1,32 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { GetAptosTransactions } from "./GetAptosTransactions"; +import { GetAptosSequences } from "./GetAptosSequences"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; -export class PollAptosTransactions extends RunPollingJob { +export class PollAptos extends RunPollingJob { protected readonly logger: Logger; + private readonly getAptos: GetAptosSequences; private lastSequence?: bigint; private sequenceHeightCursor?: bigint; private previousSequence?: bigint; + private getAptosRecords: { [key: string]: any } = { + GetAptosSequences, + GetAptosTransactions, + }; constructor( private readonly cfg: PollAptosTransactionsConfig, private readonly statsRepo: StatRepository, private readonly metadataRepo: MetadataRepository, - private readonly repo: AptosRepository + private readonly repo: AptosRepository, + getAptos: string ) { super(cfg.id, statsRepo, cfg.interval); this.logger = winston.child({ module: "PollAptos", label: this.cfg.id }); + this.getAptos = new this.getAptosRecords[getAptos ?? "GetAptosSequences"](repo); } protected async preHook(): Promise { @@ -36,7 +45,7 @@ export class PollAptosTransactions extends RunPollingJob { this.sequenceHeightCursor >= BigInt(this.cfg.toSequence) ) { this.logger.info( - `[aptos][PollAptosTransactions] Finished processing all transactions from sequence ${this.cfg.fromSequence} to ${this.cfg.toSequence}` + `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.fromSequence} to ${this.cfg.toSequence}` ); return false; } @@ -44,75 +53,26 @@ export class PollAptosTransactions extends RunPollingJob { return true; } - protected async get(): Promise { - let populatedTransactions: TransactionsByVersion[] = []; - - const filter = this.cfg.filter; - const range = this.getBlockRange(); - - const batches = this.createBatches(range); - - for (const batch of batches) { - const events = await this.repo.getSequenceNumber( - { - fromSequence: range?.fromSequence, - toSequence: batch, - }, - filter - ); - - // save previous sequence with last sequence and update last sequence with the new sequence - this.previousSequence = this.lastSequence; - this.lastSequence = BigInt(events[events.length - 1].sequence_number); - - const transactions = await this.repo.getTransactionsForVersions(events, filter); - transactions.forEach((tx) => { - populatedTransactions.push(tx); - }); - } - - this.logger.info( - `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${this.cfg.addresses}][blocks:${range?.fromSequence} - ${range?.toSequence}]` + protected async get(): Promise { + const range = this.getAptos.getBlockRange( + this.cfg.getBlockBatchSize(), + this.cfg.fromSequence, + this.previousSequence, + this.lastSequence ); - return populatedTransactions; - } - private getBlockRange(): Sequence | undefined { - // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size - if (this.cfg.fromSequence) { - return { - fromSequence: Number(this.cfg.fromSequence), - toSequence: this.cfg.getBlockBatchSize(), - }; - } + const records = await this.getAptos.execute(range, { + addresses: this.cfg.addresses, + filter: this.cfg.filter, + previousSequence: this.previousSequence, + lastSequence: this.lastSequence, + }); - if (this.previousSequence && this.lastSequence) { - // if process the [same sequence], return the same last sequence and the to sequence equal 1 - if (this.previousSequence === this.lastSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, - }; - } - - // if process [different sequences], return the difference between the last sequence and the previous sequence plus 1 - if (this.previousSequence !== this.lastSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: Number(this.lastSequence) - Number(this.previousSequence) + 1, - }; - } - } + const updatedRange = this.getAptos.updatedRange(); + this.previousSequence = updatedRange?.previousSequence; + this.lastSequence = updatedRange?.lastSequence; - if (this.lastSequence) { - // if there is [no previous sequence], return the last sequence and the to sequence equal the block batch size - if (!this.cfg.fromSequence || BigInt(this.cfg.fromSequence) < this.lastSequence) { - return { - fromSequence: Number(this.lastSequence), - toSequence: this.cfg.getBlockBatchSize(), - }; - } - } + return records; } protected async persist(): Promise { @@ -141,25 +101,6 @@ export class PollAptosTransactions extends RunPollingJob { type: "current", }); } - - private createBatches(range: Sequence | undefined): number[] { - let batchSize = 100; - let total = 1; - - if (range && range.toSequence) { - batchSize = range.toSequence < batchSize ? range.toSequence : batchSize; - total = range.toSequence ?? total; - } - - const numBatches = Math.ceil(total / batchSize); - const batches: number[] = []; - - for (let i = 0; i < numBatches; i++) { - batches.push(batchSize); - } - - return batches; - } } export class PollAptosTransactionsConfig { @@ -222,9 +163,17 @@ export type TransactionFilter = { fieldName: string; address: string; event: string; + type: string; }; export type Sequence = { fromSequence?: number; toSequence?: number; }; + +export type Filter = { + fieldName: string; + address: string; + event: string; + type: string; +}; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 7f4f96f54..966733bbe 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -20,7 +20,7 @@ import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { AptosEvent } from "./entities/aptos"; import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -import { Sequence, TransactionFilter } from "./actions/aptos/PollAptosTransactions"; +import { Sequence, TransactionFilter } from "./actions/aptos/PollAptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -71,6 +71,7 @@ export interface SuiRepository { } export interface AptosRepository { + getTransactions(limit: number): Promise; getSequenceNumber(range: Sequence | undefined, filter: TransactionFilter): Promise; getTransactionsForVersions( events: AptosEvent[], diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index f3eb7295d..91eaf46a0 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -19,17 +19,17 @@ export const aptosLogMessagePublishedMapper = ( return { name: "log-message-published", - address: tx.address!, + address: tx.address, chainId: CHAIN_ID_APTOS, - txHash: tx.hash!, - blockHeight: tx.blockHeight!, - blockTime: tx.blockTime, + txHash: tx.hash, + blockHeight: tx.blockHeight, + blockTime: tx.timestamp, attributes: { - sender: tx.sender!, - sequence: Number(tx.sequence!), - payload: tx.payload!, + sender: tx.sender, + sequence: Number(tx.sequence), + payload: tx.payload, nonce: Number(tx.nonce), - consistencyLevel: tx.consistencyLevel!, + consistencyLevel: tx.consistencyLevel, }, }; }; diff --git a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts index c15f4455b..e76049054 100644 --- a/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/StaticJobRepository.ts @@ -35,10 +35,10 @@ import { PollSuiTransactionsConfig, } from "../../domain/actions/sui/PollSuiTransactions"; import { - PollAptosTransactions, + PollAptos, PollAptosTransactionsConfig, PollAptosTransactionsConfigProps, -} from "../../domain/actions/aptos/PollAptosTransactions"; +} from "../../domain/actions/aptos/PollAptos"; import { HandleAptosTransactions } from "../../domain/actions/aptos/HandleAptosTransactions"; import { aptosLogMessagePublishedMapper } from "../mappers/aptos/aptosLogMessagePublishedMapper"; import { aptosRedeemedTransactionFoundMapper } from "../mappers/aptos/aptosRedeemedTransactionFoundMapper"; @@ -157,8 +157,8 @@ export class StaticJobRepository implements JobRepository { this.suiRepo ); - const pollAptosTransactions = (jobDef: JobDefinition) => - new PollAptosTransactions( + const pollAptos = (jobDef: JobDefinition) => + new PollAptos( new PollAptosTransactionsConfig({ ...(jobDef.source.config as PollAptosTransactionsConfigProps), id: jobDef.id, @@ -166,12 +166,13 @@ export class StaticJobRepository implements JobRepository { }), this.statsRepo, this.metadataRepo, - this.aptosRepo + this.aptosRepo, + jobDef.source.records ); this.sources.set("PollEvm", pollEvm); this.sources.set("PollSolanaTransactions", pollSolanaTransactions); this.sources.set("PollSuiTransactions", pollSuiTransactions); - this.sources.set("PollAptosTransactions", pollAptosTransactions); + this.sources.set("PollAptos", pollAptos); // Mappers this.mappers.set("evmLogMessagePublishedMapper", evmLogMessagePublishedMapper); diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index fce9fa901..5b720b7f8 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,4 +1,4 @@ -import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptosTransactions"; +import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; import { coalesceChainId } from "@certusone/wormhole-sdk/lib/cjs/utils/consts"; import { AptosEvent } from "../../../domain/entities/aptos"; @@ -8,7 +8,7 @@ export class AptosJsonRPCBlockRepository { private readonly logger: winston.Logger; constructor(private readonly client: InstrumentedAptosProvider) { - this.logger = winston.child({ module: "AptossonRPCBlockRepository" }); + this.logger = winston.child({ module: "AptosJsonRPCBlockRepository" }); } async getSequenceNumber( @@ -44,19 +44,21 @@ export class AptosJsonRPCBlockRepository { const transaction = await this.client.getTransactionByVersion(Number(event.version)); const block = await this.client.getBlockByVersion(Number(event.version)); + const wormholeEvent = transaction.events.find((tx: any) => tx.type === filter.type); + const tx = { consistencyLevel: event.data.consistency_level, blockHeight: block.block_height, - timestamp: transaction.timestamp, - blockTime: block.block_timestamp, - sequence: transaction.sequence_number, + timestamp: wormholeEvent.data.timestamp, + blockTime: wormholeEvent.data.timestamp, + sequence: wormholeEvent.data.sequence, version: transaction.version, - payload: transaction.payload, + payload: wormholeEvent.data.payload, address: filter.address, - sender: transaction.sender, + sender: wormholeEvent.data.sender, status: transaction.success, events: transaction.events, - nonce: event.data.nonce, + nonce: wormholeEvent.data.nonce, hash: transaction.hash, }; transactionsByVersion.push(tx); @@ -64,7 +66,17 @@ export class AptosJsonRPCBlockRepository { return transactionsByVersion; } catch (e) { - this.handleError(e, "getSequenceNumber"); + this.handleError(e, "getTransactionsForVersions"); + throw e; + } + } + + async getTransactions(limit: number): Promise { + try { + const results = await this.client.getTransactions(limit); + return results; + } catch (e) { + this.handleError(e, "getTransactions"); throw e; } } @@ -75,19 +87,19 @@ export class AptosJsonRPCBlockRepository { } export type TransactionsByVersion = { - consistencyLevel?: number; - blockHeight?: bigint; - timestamp?: string; + consistencyLevel: number; + blockHeight: bigint; + timestamp: number; blockTime: number; - sequence?: string; - version?: string; - payload?: string; - address?: string; - sender?: string; + sequence: string; + version: string; + payload: string; + address: string; + sender: string; status?: boolean; - events?: any; - nonce?: string; - hash?: string; + events: any; + nonce: string; + hash: string; }; // TODO: Remove diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index bfb3eb0c4..eeafe8c20 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -1,4 +1,4 @@ -import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptosTransactions"; +import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; import { TransactionsByVersion } from "./AptosJsonRPCBlockRepository"; import { AptosRepository } from "../../../domain/repositories"; @@ -27,4 +27,8 @@ export class RateLimitedAptosJsonRPCBlockRepository .fn(() => this.delegate.getTransactionsForVersions(events, filter)) .execute(); } + + getTransactions(limit: number): Promise { + return this.breaker.fn(() => this.delegate.getTransactions(limit)).execute(); + } } diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index fb8eceeef..9f39f3032 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -37,6 +37,7 @@ export class InstrumentedAptosProvider { const params = fromSequence ? { start: fromSequence, limit: toSequence } : { limit: toSequence }; + const result = await this.client.getEventsByEventHandle( address, eventHandle, @@ -66,6 +67,15 @@ export class InstrumentedAptosProvider { throw e; } } + + public async getTransactions(limit: number): Promise { + try { + const result = await this.client.getTransactions({ limit }); + return result; + } catch (e) { + throw e; + } + } } export type HttpClientOptions = { diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index 606c5ca99..e3eb96678 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -99,8 +99,8 @@ txs = [ { consistencyLevel: 0, blockHeight: 153517771n, - timestamp: "1709638693443328", - blockTime: 1709638693443328, + timestamp: 170963869344, + blockTime: 170963869344, sequence: "34", version: "482649547", payload: diff --git a/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts index 8990d9540..aaf65f294 100644 --- a/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts @@ -1,8 +1,8 @@ import { - PollAptosTransactions, + PollAptos, PollAptosTransactionsConfig, PollAptosTransactionsMetadata, -} from "../../../../src/domain/actions/aptos/PollAptosTransactions"; +} from "../../../../src/domain/actions/aptos/PollAptos"; import { afterEach, describe, it, expect, jest } from "@jest/globals"; import { AptosRepository, @@ -28,7 +28,7 @@ let handlers = { working: (txs: TransactionsByVersion[]) => Promise.resolve(), failing: (txs: TransactionsByVersion[]) => Promise.reject(), }; -let pollAptosTransactions: PollAptosTransactions; +let pollAptos: PollAptos; let props = { blockBatchSize: 100, @@ -45,6 +45,7 @@ let props = { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", }, chain: "aptos", id: "poll-log-message-published-aptos", @@ -52,9 +53,9 @@ let props = { let cfg = new PollAptosTransactionsConfig(props); -describe("pollAptosTransactions", () => { +describe("PollAptos", () => { afterEach(async () => { - await pollAptosTransactions.stop(); + await pollAptos.stop(); }); it("should be not generate range (from and to sequence) and search the latest sequence plus block batch size cfg", async () => { @@ -75,6 +76,7 @@ describe("pollAptosTransactions", () => { event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", fieldName: "event", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) @@ -101,6 +103,7 @@ describe("pollAptosTransactions", () => { event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", fieldName: "event", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) @@ -121,12 +124,13 @@ describe("pollAptosTransactions", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: 146040, toSequence: 1 }, + { fromSequence: 146040, toSequence: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", fieldName: "event", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) @@ -153,6 +157,7 @@ describe("pollAptosTransactions", () => { event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", fieldName: "event", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) @@ -179,6 +184,7 @@ describe("pollAptosTransactions", () => { event: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", fieldName: "event", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) @@ -212,8 +218,8 @@ const givenEvmBlockRepository = () => { { consistencyLevel: 0, blockHeight: 153517771n, - timestamp: "1709638693443328", - blockTime: 1709638693443328, + timestamp: 170963869344, + blockTime: 170963869344, sequence: "34", version: "482649547", payload: @@ -287,6 +293,7 @@ const givenEvmBlockRepository = () => { aptosRepo = { getSequenceNumber: () => Promise.resolve(events), getTransactionsForVersions: () => Promise.resolve(txs), + getTransactions: () => Promise.resolve(txs), }; getSequenceNumberSpy = jest.spyOn(aptosRepo, "getSequenceNumber"); @@ -311,9 +318,9 @@ const givenStatsRepository = () => { }; const givenPollAptosTx = (cfg: PollAptosTransactionsConfig) => { - pollAptosTransactions = new PollAptosTransactions(cfg, statsRepo, metadataRepo, aptosRepo); + pollAptos = new PollAptos(cfg, statsRepo, metadataRepo, aptosRepo, "GetAptosSequences"); }; const whenPollEvmLogsStarts = async () => { - pollAptosTransactions.run([handlers.working]); + pollAptos.run([handlers.working]); }; diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index d164615f9..b8904d560 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -30,8 +30,8 @@ describe("aptosLogMessagePublishedMapper", () => { const txs: TransactionsByVersion = { consistencyLevel: 0, blockHeight: 153517771n, - timestamp: "1709638693443328", - blockTime: 1709638693443328, + timestamp: 170963869344, + blockTime: 170963869344, sequence: "34", version: "482649547", payload: diff --git a/deploy/blockchain-watcher/workers/source-events-1.yaml b/deploy/blockchain-watcher/workers/source-events-1.yaml index 3fee49b9c..ae5796d68 100644 --- a/deploy/blockchain-watcher/workers/source-events-1.yaml +++ b/deploy/blockchain-watcher/workers/source-events-1.yaml @@ -340,18 +340,19 @@ data: "id": "poll-log-message-published-aptos", "chain": "aptos", "source": { - "action": "PollAptosTransactions", + "action": "PollAptos", "config": { "blockBatchSize": 100, "commitment": "finalized", - "interval": 5000, + "interval": 15000, "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], "chain": "aptos", "chainId": 22, "filter": { "fieldName": "event", "address": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", - "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle" + "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + "type": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage" } } }, @@ -589,25 +590,26 @@ data: "id": "poll-log-message-published-aptos", "chain": "aptos", "source": { - "action": "PollAptosTransactions", + "action": "PollAptos", "config": { "blockBatchSize": 100, "commitment": "finalized", - "interval": 5000, + "interval": 15000, "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], "chain": "aptos", "chainId": 22, "filter": { "fieldName": "event", "address": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", - "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle" + "event": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", + "type": "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage" } } }, "handlers": [ { "action": "HandleAptosTransactions", - "target": "dummy", + "target": "sns", "mapper": "aptosLogMessagePublishedMapper", "config": { "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", diff --git a/deploy/blockchain-watcher/workers/source-events.yaml b/deploy/blockchain-watcher/workers/source-events.yaml index 4113bfc47..731ec925f 100644 --- a/deploy/blockchain-watcher/workers/source-events.yaml +++ b/deploy/blockchain-watcher/workers/source-events.yaml @@ -247,7 +247,7 @@ data: "source": { "action": "PollEvm", "config": { - "blockBatchSize": 100, + "blockBatchSize": 50, "commitment": "latest", "interval": 15000, "addresses": ["0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D"], diff --git a/deploy/blockchain-watcher/workers/target-events-1.yaml b/deploy/blockchain-watcher/workers/target-events-1.yaml index 4a39d67b3..4126f069d 100644 --- a/deploy/blockchain-watcher/workers/target-events-1.yaml +++ b/deploy/blockchain-watcher/workers/target-events-1.yaml @@ -40,7 +40,7 @@ data: "id": "poll-log-message-published-aptos", "chain": "aptos", "source": { - "action": "PollAptosTransactions", + "action": "PollAptos", "config": { "blockBatchSize": 100, "commitment": "finalized", @@ -74,7 +74,7 @@ data: "id": "poll-log-message-published-aptos", "chain": "aptos", "source": { - "action": "PollAptosTransactions", + "action": "PollAptos", "config": { "blockBatchSize": 100, "commitment": "finalized", From 9367db12c70d39dbc4abea45007664cfb6df5afa Mon Sep 17 00:00:00 2001 From: julian merlo Date: Fri, 8 Mar 2024 09:52:03 -0300 Subject: [PATCH 09/30] Improve implementations and create test --- .../domain/actions/aptos/GetAptosSequences.ts | 97 +++-- .../actions/aptos/GetAptosTransactions.ts | 131 ++++++ .../actions/aptos/HandleAptosTransactions.ts | 7 +- .../src/domain/actions/aptos/PollAptos.ts | 73 ++-- .../src/domain/entities/aptos.ts | 5 + blockchain-watcher/src/domain/repositories.ts | 12 +- .../aptosRedeemedTransactionFoundMapper.ts | 36 +- .../mappers/contractsMapperConfig.json | 2 +- .../aptos/AptosJsonRPCBlockRepository.ts | 115 +++-- .../RateLimitedAptosJsonRPCBlockRepository.ts | 21 +- .../rpc/http/InstrumentedAptosProvider.ts | 33 +- ...ions.test.ts => GetAptosSequences.test.ts} | 111 +++-- .../aptos/GetAptosTransactions.test.ts | 392 ++++++++++++++++++ .../aptos/HandleAptosTransactions.test.ts | 7 +- .../aptosLogMessagePublishedMapper.test.ts | 11 +- ...ptosRedeemedTransactionFoundMapper.test.ts | 109 +++++ .../workers/source-events-1.yaml | 4 +- .../workers/target-events-1.yaml | 24 +- .../workers/target-events.yaml | 2 +- 19 files changed, 964 insertions(+), 228 deletions(-) create mode 100644 blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts rename blockchain-watcher/test/domain/actions/aptos/{PollAptosTransactions.test.ts => GetAptosSequences.test.ts} (76%) create mode 100644 blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts create mode 100644 blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index 0becc5509..c6c2409e8 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -1,113 +1,120 @@ import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { Block, TransactionFilter } from "./PollAptos"; import { AptosRepository } from "../../repositories"; import winston from "winston"; -import { Filter, Sequence } from "./PollAptos"; export class GetAptosSequences { - private readonly repo: AptosRepository; protected readonly logger: winston.Logger; + private readonly repo: AptosRepository; - private lastSequence?: bigint; - private previousSequence?: bigint; + private lastBlock?: bigint; + private previousBlock?: bigint; constructor(repo: AptosRepository) { this.logger = winston.child({ module: "GetAptosSequences" }); this.repo = repo; } - async execute(range: Sequence | undefined, opts: GetAptosOpts): Promise { + async execute(range: Block | undefined, opts: GetAptosOpts): Promise { let populatedTransactions: TransactionsByVersion[] = []; + this.logger.info( + `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` + ); + const batches = this.createBatches(range); for (const batch of batches) { const events = await this.repo.getSequenceNumber( { - fromSequence: range?.fromSequence, - toSequence: batch, + fromBlock: range?.fromBlock, + toBlock: batch, }, opts.filter ); - // update last sequence with the new sequence - this.lastSequence = BigInt(events[events.length - 1].sequence_number); + // update last block with the new block + this.lastBlock = BigInt(events[events.length - 1].sequence_number); - if (opts.previousSequence == this.lastSequence) { + if (opts.previousBlock == this.lastBlock) { return []; } - // save previous sequence with last sequence - this.previousSequence = opts.lastSequence; + // save previous block with last block + this.previousBlock = opts.lastBlock; - const transactions = await this.repo.getTransactionsForVersions(events, opts.filter); + const transactions = await this.repo.getTransactionsByVersionsForSourceEvent( + events, + opts.filter + ); transactions.forEach((tx) => { populatedTransactions.push(tx); }); } this.logger.info( - `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][sequence: ${range?.fromSequence}]` + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][block: ${range?.fromBlock}]` ); return populatedTransactions; } getBlockRange( cfgBlockBarchSize: number, - cfgFromSequence: bigint | undefined, + cfgFromBlock: bigint | undefined, savedPreviousSequence: bigint | undefined, - savedLastSequence: bigint | undefined - ): Sequence | undefined { - // if [set up a from sequence for cfg], return the from sequence and the to sequence equal the block batch size - if (cfgFromSequence) { + savedLastBlock: bigint | undefined + ): Block | undefined { + // if [set up a from block for cfg], return the from block and the to block equal the block batch size + if (cfgFromBlock) { return { - fromSequence: Number(cfgFromSequence), - toSequence: cfgBlockBarchSize, + fromBlock: Number(cfgFromBlock), + toBlock: cfgBlockBarchSize, }; } - if (savedPreviousSequence && savedLastSequence) { - // if process the [same sequence], return the same last sequence and the to sequence equal the block batch size - if (savedPreviousSequence === savedLastSequence) { + if (savedPreviousSequence && savedLastBlock) { + // if process the [same block], return the same last block and the to block equal the block batch size + if (savedPreviousSequence === savedLastBlock) { return { - fromSequence: Number(savedLastSequence), - toSequence: cfgBlockBarchSize, + fromBlock: Number(savedLastBlock), + toBlock: cfgBlockBarchSize, }; } - // if process [different sequences], return the difference between the last sequence and the previous sequence plus 1 - if (savedPreviousSequence !== savedLastSequence) { + // if process [different sequences], return the difference between the last block and the previous block plus 1 + if (savedPreviousSequence !== savedLastBlock) { return { - fromSequence: Number(savedLastSequence), - toSequence: Number(savedLastSequence) - Number(savedPreviousSequence) + 1, + fromBlock: Number(savedLastBlock), + toBlock: Number(savedLastBlock) - Number(savedPreviousSequence) + 1, }; } } - if (savedLastSequence) { - // if there is [no previous sequence], return the last sequence and the to sequence equal the block batch size - if (!cfgFromSequence || BigInt(cfgFromSequence) < savedLastSequence) { + if (savedLastBlock) { + // if there is [no previous block], return the last block and the to block equal the block batch size + if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { return { - fromSequence: Number(savedLastSequence), - toSequence: cfgBlockBarchSize, + fromBlock: Number(savedLastBlock), + toBlock: cfgBlockBarchSize, }; } } } - updatedRange() { + getUpdatedRange() { return { - previousSequence: this.previousSequence, - lastSequence: this.lastSequence, + previousBlock: this.previousBlock, + lastBlock: this.lastBlock, }; } - private createBatches(range: Sequence | undefined): number[] { + private createBatches(range: Block | undefined): number[] { let batchSize = 100; let total = 1; - if (range && range.toSequence) { - batchSize = range.toSequence < batchSize ? range.toSequence : batchSize; - total = range.toSequence ?? total; + if (range && range.toBlock) { + batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; + total = range.toBlock ?? total; } const numBatches = Math.ceil(total / batchSize); @@ -123,7 +130,7 @@ export class GetAptosSequences { export type GetAptosOpts = { addresses: string[]; - filter: Filter; - previousSequence?: bigint | undefined; - lastSequence?: bigint | undefined; + filter: TransactionFilter; + previousBlock?: bigint | undefined; + lastBlock?: bigint | undefined; }; diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts new file mode 100644 index 000000000..666efd705 --- /dev/null +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -0,0 +1,131 @@ +import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosRepository } from "../../repositories"; +import winston from "winston"; +import { Block, TransactionFilter } from "./PollAptos"; + +export class GetAptosTransactions { + private readonly repo: AptosRepository; + protected readonly logger: winston.Logger; + + private lastBlock?: bigint; + private previousBlock?: bigint; + + constructor(repo: AptosRepository) { + this.logger = winston.child({ module: "GetAptosTransactions" }); + this.repo = repo; + } + + async execute(range: Block | undefined, opts: GetAptosOpts): Promise { + let populatedTransactions: TransactionsByVersion[] = []; + + this.logger.info( + `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` + ); + + const batches = this.createBatches(range); + + for (const toBatch of batches) { + const fromBatch = this.lastBlock ? Number(this.lastBlock) : range?.fromBlock; + + const transaction = await this.repo.getTransactions({ + fromBlock: fromBatch, + toBlock: toBatch, + }); + + // Only process transactions to the contract address configured + const transactionsByAddressConfigured = transaction.filter((transaction) => + opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) + ); + + // update last block with the new block + this.lastBlock = BigInt(transaction[transaction.length - 1].version); + + if (opts.previousBlock == this.lastBlock) { + return []; + } + + // save previous block with last block + this.previousBlock = opts.lastBlock; + + if (transactionsByAddressConfigured.length > 0) { + const transactions = await this.repo.getTransactionsByVersionsForRedeemedEvent( + transactionsByAddressConfigured, + opts.filter + ); + transactions.forEach((tx) => { + populatedTransactions.push(tx); + }); + } + } + + return populatedTransactions; + } + + getBlockRange( + cfgBlockBarchSize: number, + cfgFromBlock: bigint | undefined, + savedPreviousBlock: bigint | undefined, + savedLastBlock: bigint | undefined + ): Block | undefined { + // if [set up a from block for cfg], return the from block and the to block equal the block batch size + if (cfgFromBlock) { + return { + fromBlock: Number(cfgFromBlock), + toBlock: cfgBlockBarchSize, + }; + } + + if (savedPreviousBlock && savedLastBlock) { + // if process [equal or different blocks], return the same last block and the to block equal the block batch size + if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { + return { + fromBlock: Number(savedLastBlock), + toBlock: cfgBlockBarchSize, + }; + } + } + + if (savedLastBlock) { + // if there is [no previous block], return the last block and the to block equal the block batch size + if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { + return { + fromBlock: Number(savedLastBlock), + toBlock: cfgBlockBarchSize, + }; + } + } + } + + getUpdatedRange() { + return { + previousBlock: this.previousBlock, + lastBlock: this.lastBlock, + }; + } + + private createBatches(range: Block | undefined): number[] { + let batchSize = 100; + let total = 1; + + if (range && range.toBlock) { + batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; + total = range.toBlock ?? total; + } + + const numBatches = Math.ceil(total / batchSize); + const batches: number[] = []; + + for (let i = 0; i < numBatches; i++) { + batches.push(batchSize); + } + + return batches; + } +} + +export type GetAptosOpts = { + addresses: string[]; + filter: TransactionFilter; + previousBlock?: bigint | undefined; + lastBlock?: bigint | undefined; +}; diff --git a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts index 61b26afc0..80afadcbb 100644 --- a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts @@ -1,6 +1,9 @@ import { TransactionFoundEvent } from "../../entities"; import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { StatRepository } from "../../repositories"; +import winston from "winston"; + +let logger: winston.Logger = winston.child({ module: "HandleAptosTransactions" }); export class HandleAptosTransactions { constructor( @@ -31,9 +34,11 @@ export class HandleAptosTransactions { job: this.cfg.id, chain: "aptos", commitment: "finalized", - protocol: protocol ?? "unknown", + protocol: protocol, }; + logger.debug(`[aptos] Build labels: [labels: ${JSON.stringify(labels)}]`); + this.statsRepo.count(this.cfg.metricName, labels); } } diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index 4d7e5aa88..cb83ec4b6 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -9,9 +9,9 @@ export class PollAptos extends RunPollingJob { protected readonly logger: Logger; private readonly getAptos: GetAptosSequences; - private lastSequence?: bigint; + private lastBlock?: bigint; private sequenceHeightCursor?: bigint; - private previousSequence?: bigint; + private previousBlock?: bigint; private getAptosRecords: { [key: string]: any } = { GetAptosSequences, GetAptosTransactions, @@ -32,9 +32,9 @@ export class PollAptos extends RunPollingJob { protected async preHook(): Promise { const metadata = await this.metadataRepo.get(this.cfg.id); if (metadata) { - this.sequenceHeightCursor = metadata.lastSequence; - this.previousSequence = metadata.previousSequence; - this.lastSequence = metadata.lastSequence; + this.sequenceHeightCursor = metadata.lastBlock; + this.previousBlock = metadata.previousBlock; + this.lastBlock = metadata.lastBlock; } } @@ -45,7 +45,7 @@ export class PollAptos extends RunPollingJob { this.sequenceHeightCursor >= BigInt(this.cfg.toSequence) ) { this.logger.info( - `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.fromSequence} to ${this.cfg.toSequence}` + `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.fromBlock} to ${this.cfg.toSequence}` ); return false; } @@ -56,31 +56,37 @@ export class PollAptos extends RunPollingJob { protected async get(): Promise { const range = this.getAptos.getBlockRange( this.cfg.getBlockBatchSize(), - this.cfg.fromSequence, - this.previousSequence, - this.lastSequence + this.cfg.fromBlock, + this.previousBlock, + this.lastBlock ); const records = await this.getAptos.execute(range, { addresses: this.cfg.addresses, filter: this.cfg.filter, - previousSequence: this.previousSequence, - lastSequence: this.lastSequence, + previousBlock: this.previousBlock, + lastBlock: this.lastBlock, }); - const updatedRange = this.getAptos.updatedRange(); - this.previousSequence = updatedRange?.previousSequence; - this.lastSequence = updatedRange?.lastSequence; + this.updateBlockRange(); return records; } + private updateBlockRange(): void { + // Update the previousBlock and lastBlock based on the executed range + const updatedRange = this.getAptos.getUpdatedRange(); + if (updatedRange) { + this.previousBlock = updatedRange.previousBlock; + this.lastBlock = updatedRange.lastBlock; + } + } + protected async persist(): Promise { - this.lastSequence = this.lastSequence; - if (this.lastSequence) { + if (this.lastBlock) { await this.metadataRepo.save(this.cfg.id, { - lastSequence: this.lastSequence, - previousSequence: this.previousSequence, + previousBlock: this.previousBlock, + lastBlock: this.lastBlock, }); } } @@ -92,7 +98,7 @@ export class PollAptos extends RunPollingJob { commitment: this.cfg.getCommitment(), }; this.statsRepo.count("job_execution", labels); - this.statsRepo.measure("polling_cursor", this.lastSequence ?? 0n, { + this.statsRepo.measure("polling_cursor", this.lastBlock ?? 0n, { ...labels, type: "max", }); @@ -122,8 +128,8 @@ export class PollAptosTransactionsConfig { return this.props.interval; } - public get fromSequence(): bigint | undefined { - return this.props.fromSequence ? BigInt(this.props.fromSequence) : undefined; + public get fromBlock(): bigint | undefined { + return this.props.fromBlock ? BigInt(this.props.fromBlock) : undefined; } public get toSequence(): bigint | undefined { @@ -141,7 +147,7 @@ export class PollAptosTransactionsConfig { export interface PollAptosTransactionsConfigProps { blockBatchSize?: number; - fromSequence?: bigint; + fromBlock?: bigint; toSequence?: bigint; environment: string; commitment?: string; @@ -155,25 +161,18 @@ export interface PollAptosTransactionsConfigProps { } export type PollAptosTransactionsMetadata = { - previousSequence?: bigint; - lastSequence?: bigint; + previousBlock?: bigint; + lastBlock?: bigint; }; export type TransactionFilter = { - fieldName: string; + fieldName?: string; address: string; - event: string; - type: string; + event?: string; + type?: string; }; -export type Sequence = { - fromSequence?: number; - toSequence?: number; -}; - -export type Filter = { - fieldName: string; - address: string; - event: string; - type: string; +export type Block = { + fromBlock?: number; + toBlock?: number; }; diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index f32a487a3..0a1924d8c 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -11,3 +11,8 @@ export type AptosEvent = Omit & { timestamp: string; }; }; + +export enum TxStatus { + Confirmed = "success", + Failed = "failed", +} diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 966733bbe..cd2c5e540 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -20,7 +20,7 @@ import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { AptosEvent } from "./entities/aptos"; import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -import { Sequence, TransactionFilter } from "./actions/aptos/PollAptos"; +import { Block, TransactionFilter } from "./actions/aptos/PollAptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -71,9 +71,13 @@ export interface SuiRepository { } export interface AptosRepository { - getTransactions(limit: number): Promise; - getSequenceNumber(range: Sequence | undefined, filter: TransactionFilter): Promise; - getTransactionsForVersions( + getTransactions(range: Block | undefined): Promise; + getSequenceNumber(range: Block | undefined, filter: TransactionFilter): Promise; + getTransactionsByVersionsForSourceEvent( + events: AptosEvent[], + filter: TransactionFilter + ): Promise; + getTransactionsByVersionsForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter ): Promise; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index b5adbf652..f70f3324b 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -1,11 +1,41 @@ import { TransactionFoundEvent } from "../../../domain/entities"; -import { AptosEvent } from "../../../domain/entities/aptos"; +import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; +import { findProtocol } from "../contractsMapper"; +import { CHAIN_ID_APTOS } from "@certusone/wormhole-sdk"; +import { TxStatus } from "../../../domain/entities/aptos"; import winston from "winston"; let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFoundMapper" }); +const APTOS_CHAIN = "aptos"; + export const aptosRedeemedTransactionFoundMapper = ( - tx: AptosEvent[] + tx: TransactionsByVersion ): TransactionFoundEvent | undefined => { - return undefined; + const emitterAddress = tx.sender; + + const protocol = findProtocol(APTOS_CHAIN, tx.address, tx.type!, tx.hash); + + logger.info( + `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${tx.emitterChain}/${emitterAddress}/${tx.sequence}]` + ); + + if (protocol && protocol.type && protocol.method) { + return { + name: "transfer-redeemed", + address: tx.address, + blockHeight: tx.blockHeight, + blockTime: tx.blockTime, + chainId: CHAIN_ID_APTOS, + txHash: tx.hash, + attributes: { + from: tx.sender, + emitterChain: tx.emitterChain, + emitterAddress: emitterAddress, + sequence: Number(tx.sequence), + status: tx?.status === true ? TxStatus.Confirmed : TxStatus.Failed, + protocol: protocol.method, + }, + }; + } }; diff --git a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json index 93a16a3f7..984c6a10c 100644 --- a/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json +++ b/blockchain-watcher/src/infrastructure/mappers/contractsMapperConfig.json @@ -914,7 +914,7 @@ "type": "Token Bridge", "methods": [ { - "methodId": "complete_transfer::submit_vaa_and_register_entry", + "methodId": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry", "method": "Token Bridge" } ] diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 5b720b7f8..0232ed858 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,6 +1,6 @@ -import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; +import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; -import { coalesceChainId } from "@certusone/wormhole-sdk/lib/cjs/utils/consts"; +import { parseVaa } from "@certusone/wormhole-sdk"; import { AptosEvent } from "../../../domain/entities/aptos"; import winston from "winston"; @@ -12,18 +12,18 @@ export class AptosJsonRPCBlockRepository { } async getSequenceNumber( - range: Sequence | undefined, + range: Block | undefined, filter: TransactionFilter ): Promise { try { - const fromSequence = range?.fromSequence ? Number(range?.fromSequence) : undefined; - const toSequence = range?.toSequence ? Number(range?.toSequence) : undefined; + const fromBlock = range?.fromBlock ? Number(range?.fromBlock) : undefined; + const toSequence = range?.toBlock ? Number(range?.toBlock) : undefined; const results = await this.client.getEventsByEventHandle( filter.address, - filter.event, + filter.event!, filter.fieldName, - fromSequence, + fromBlock, toSequence ); return results; @@ -33,47 +33,86 @@ export class AptosJsonRPCBlockRepository { } } - async getTransactionsForVersions( + async getTransactionsByVersionsForSourceEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { try { - const transactionsByVersion: TransactionsByVersion[] = []; + const transactions = await Promise.all( + events.map(async (event) => { + const transaction = await this.client.getTransactionByVersion(Number(event.version)); + const block = await this.client.getBlockByVersion(Number(event.version)); - for (const event of events) { - const transaction = await this.client.getTransactionByVersion(Number(event.version)); - const block = await this.client.getBlockByVersion(Number(event.version)); + const wormholeEvent = transaction.events.find((tx: any) => tx.type === filter.type); - const wormholeEvent = transaction.events.find((tx: any) => tx.type === filter.type); + return { + consistencyLevel: event.data.consistency_level, + blockHeight: block.block_height, + timestamp: wormholeEvent.data.timestamp, + blockTime: wormholeEvent.data.timestamp, + sequence: wormholeEvent.data.sequence, + version: transaction.version, + payload: wormholeEvent.data.payload, + address: filter.address, + sender: wormholeEvent.data.sender, + status: transaction.success, + events: transaction.events, + nonce: wormholeEvent.data.nonce, + hash: transaction.hash, + }; + }) + ); - const tx = { - consistencyLevel: event.data.consistency_level, - blockHeight: block.block_height, - timestamp: wormholeEvent.data.timestamp, - blockTime: wormholeEvent.data.timestamp, - sequence: wormholeEvent.data.sequence, - version: transaction.version, - payload: wormholeEvent.data.payload, - address: filter.address, - sender: wormholeEvent.data.sender, - status: transaction.success, - events: transaction.events, - nonce: wormholeEvent.data.nonce, - hash: transaction.hash, - }; - transactionsByVersion.push(tx); - } + return transactions; + } catch (e) { + this.handleError(e, "getTransactionsForVersions"); + throw e; + } + } - return transactionsByVersion; + async getTransactionsByVersionsForRedeemedEvent( + events: AptosEvent[], + filter: TransactionFilter + ): Promise { + try { + const transactions = await Promise.all( + events.map(async (event) => { + const transaction = await this.client.getTransactionByVersion(Number(event.version)); + const block = await this.client.getBlockByVersion(Number(event.version)); + + const vaaBuffer = Buffer.from(transaction.payload.arguments[0].substring(2), "hex"); + const vaa = parseVaa(vaaBuffer); + + return { + consistencyLevel: vaa.consistencyLevel, + emitterChain: vaa.emitterChain, + blockHeight: block.block_height, + timestamp: vaa.timestamp, + blockTime: vaa.timestamp, + sequence: vaa.sequence, + version: transaction.version, + payload: vaa.payload.toString("hex"), + address: filter.address, + sender: vaa.emitterAddress.toString("hex"), + status: transaction.success, + events: transaction.events, + nonce: vaa.nonce, + hash: transaction.hash, + type: filter.type, + }; + }) + ); + + return transactions; } catch (e) { this.handleError(e, "getTransactionsForVersions"); throw e; } } - async getTransactions(limit: number): Promise { + async getTransactions(block: Block): Promise { try { - const results = await this.client.getTransactions(limit); + const results = await this.client.getTransactions(block); return results; } catch (e) { this.handleError(e, "getTransactions"); @@ -88,20 +127,18 @@ export class AptosJsonRPCBlockRepository { export type TransactionsByVersion = { consistencyLevel: number; + emitterChain?: number; blockHeight: bigint; timestamp: number; blockTime: number; - sequence: string; + sequence: bigint; version: string; payload: string; address: string; sender: string; status?: boolean; events: any; - nonce: string; + nonce: number; hash: string; + type?: string; }; - -// TODO: Remove -const makeVaaKey = (transactionHash: string, emitter: string, seq: string): string => - `${transactionHash}:${coalesceChainId("aptos")}/${emitter}/${seq}`; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index eeafe8c20..9dc0d1ac9 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -1,4 +1,4 @@ -import { Sequence, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; +import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; import { TransactionsByVersion } from "./AptosJsonRPCBlockRepository"; import { AptosRepository } from "../../../domain/repositories"; @@ -15,20 +15,29 @@ export class RateLimitedAptosJsonRPCBlockRepository this.logger = winston.child({ module: "RateLimitedAptosJsonRPCBlockRepository" }); } - getSequenceNumber(range: Sequence | undefined, filter: TransactionFilter): Promise { + getSequenceNumber(range: Block | undefined, filter: TransactionFilter): Promise { return this.breaker.fn(() => this.delegate.getSequenceNumber(range, filter)).execute(); } - getTransactionsForVersions( + getTransactionsByVersionsForSourceEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { return this.breaker - .fn(() => this.delegate.getTransactionsForVersions(events, filter)) + .fn(() => this.delegate.getTransactionsByVersionsForSourceEvent(events, filter)) .execute(); } - getTransactions(limit: number): Promise { - return this.breaker.fn(() => this.delegate.getTransactions(limit)).execute(); + getTransactionsByVersionsForRedeemedEvent( + events: AptosEvent[], + filter: TransactionFilter + ): Promise { + return this.breaker + .fn(() => this.delegate.getTransactionsByVersionsForRedeemedEvent(events, filter)) + .execute(); + } + + getTransactions(range: Block | undefined): Promise { + return this.breaker.fn(() => this.delegate.getTransactions(range)).execute(); } } diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index 9f39f3032..f5cd0b46b 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -1,4 +1,5 @@ import { AptosClient } from "aptos"; +import { Block } from "../../../domain/actions/aptos/PollAptos"; import winston from "winston"; type InstrumentedAptosProviderOptions = Required> & @@ -29,19 +30,17 @@ export class InstrumentedAptosProvider { public async getEventsByEventHandle( address: string, eventHandle: string, - fieldName: string, - fromSequence?: number, - toSequence: number = 100 + fieldName?: string, + fromBlock?: number, + toBlock: number = 100 ): Promise { try { - const params = fromSequence - ? { start: fromSequence, limit: toSequence } - : { limit: toSequence }; + const params = fromBlock ? { start: fromBlock, limit: toBlock } : { limit: toBlock }; const result = await this.client.getEventsByEventHandle( address, eventHandle, - fieldName, + fieldName!, params ); return result; @@ -59,6 +58,18 @@ export class InstrumentedAptosProvider { } } + public async getBlockByHeight( + blockHeight: number, + withTransactions?: boolean | undefined + ): Promise { + try { + const result = await this.client.getBlockByHeight(blockHeight, withTransactions); + return result; + } catch (e) { + throw e; + } + } + public async getBlockByVersion(version: number): Promise { try { const result = await this.client.getBlockByVersion(version); @@ -68,9 +79,13 @@ export class InstrumentedAptosProvider { } } - public async getTransactions(limit: number): Promise { + public async getTransactions(block: Block): Promise { try { - const result = await this.client.getTransactions({ limit }); + const params = 1 + ? { start: block.fromBlock, limit: block.toBlock } + : { limit: block.toBlock }; + + const result = await this.client.getTransactions(params); return result; } catch (e) { throw e; diff --git a/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts similarity index 76% rename from blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts rename to blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index aaf65f294..d232a9565 100644 --- a/blockchain-watcher/test/domain/actions/aptos/PollAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -12,8 +12,8 @@ import { import { thenWaitForAssertion } from "../../../wait-assertion"; import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -let getTransactionsForVersionsSpy: jest.SpiedFunction< - AptosRepository["getTransactionsForVersions"] +let getTransactionsByVersionsForSourceEventSpy: jest.SpiedFunction< + AptosRepository["getTransactionsByVersionsForSourceEvent"] >; let getSequenceNumberSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; @@ -32,8 +32,8 @@ let pollAptos: PollAptos; let props = { blockBatchSize: 100, - fromSequence: 0n, - toSequence: 0n, + fromBlock: 0n, + toBlock: 0n, environment: "testnet", commitment: "finalized", addresses: ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], @@ -53,24 +53,27 @@ let props = { let cfg = new PollAptosTransactionsConfig(props); -describe("PollAptos", () => { +describe("GetAptosSequences", () => { afterEach(async () => { await pollAptos.stop(); }); - it("should be not generate range (from and to sequence) and search the latest sequence plus block batch size cfg", async () => { - givenEvmBlockRepository(); + it("should be not generate range (from and to block) and search the latest block plus block batch size cfg", async () => { + // Given + givenAptosBlockRepository(); givenMetadataRepository(); givenStatsRepository(); givenPollAptosTx(cfg); + // When await whenPollEvmLogsStarts(); + // Then await thenWaitForAssertion( () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: undefined, toSequence: 100 }, + { fromBlock: undefined, toBlock: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -79,25 +82,28 @@ describe("PollAptos", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) ); }); - it("should be use fromSequence and batch size cfg from cfg", async () => { - givenEvmBlockRepository(); + it("should be use fromBlock and batch size cfg from cfg", async () => { + // Given + givenAptosBlockRepository(); givenMetadataRepository(); givenStatsRepository(); - // Se fromSequence for cfg - props.fromSequence = 146040n; + // Se fromBlock for cfg + props.fromBlock = 146040n; givenPollAptosTx(cfg); + // When await whenPollEvmLogsStarts(); + // Then await thenWaitForAssertion( () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: 146040, toSequence: 100 }, + { fromBlock: 146040, toBlock: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -106,25 +112,28 @@ describe("PollAptos", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) ); }); - it("should be return the same last sequence and the to sequence equal 1", async () => { - givenEvmBlockRepository(); - givenMetadataRepository({ previousSequence: 146040n, lastSequence: 146040n }); + it("should be return the same last block and the to block equal 100", async () => { + // Given + givenAptosBlockRepository(); + givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); givenStatsRepository(); - // Se fromSequence for cfg - props.fromSequence = 0n; + // Se fromBlock for cfg + props.fromBlock = 0n; givenPollAptosTx(cfg); + // When await whenPollEvmLogsStarts(); + // Then await thenWaitForAssertion( () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: 146040, toSequence: 100 }, + { fromBlock: 146040, toBlock: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -133,25 +142,28 @@ describe("PollAptos", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) ); }); - it("should be return the difference between the last sequence and the previous sequence plus 1", async () => { - givenEvmBlockRepository(); - givenMetadataRepository({ previousSequence: 146000n, lastSequence: 146040n }); + it("should be if return the last block and the to block equal the block batch size", async () => { + // Given + givenAptosBlockRepository(); + givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); givenStatsRepository(); - // Se fromSequence for cfg - props.fromSequence = 0n; + // Se fromBlock for cfg + props.fromBlock = 0n; givenPollAptosTx(cfg); + // When await whenPollEvmLogsStarts(); + // Then await thenWaitForAssertion( () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: 146040, toSequence: 41 }, + { fromBlock: 146040, toBlock: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -160,39 +172,12 @@ describe("PollAptos", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) - ); - }); - - it("should be if return the last sequence and the to sequence equal the block batch size", async () => { - givenEvmBlockRepository(); - givenMetadataRepository({ previousSequence: undefined, lastSequence: 146040n }); - givenStatsRepository(); - // Se fromSequence for cfg - props.fromSequence = 0n; - givenPollAptosTx(cfg); - - await whenPollEvmLogsStarts(); - - await thenWaitForAssertion( - () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), - () => - expect(getSequenceNumberSpy).toBeCalledWith( - { fromSequence: 146040, toSequence: 100 }, - { - address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", - event: - "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessageHandle", - fieldName: "event", - type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", - } - ), - () => expect(getTransactionsForVersionsSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) ); }); }); -const givenEvmBlockRepository = () => { +const givenAptosBlockRepository = () => { const events = [ { version: "481740133", @@ -220,7 +205,7 @@ const givenEvmBlockRepository = () => { blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, - sequence: "34", + sequence: 3423n, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", @@ -285,19 +270,23 @@ const givenEvmBlockRepository = () => { }, }, ], - nonce: "76704", + nonce: 76704, hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", }, ]; aptosRepo = { getSequenceNumber: () => Promise.resolve(events), - getTransactionsForVersions: () => Promise.resolve(txs), + getTransactionsByVersionsForSourceEvent: () => Promise.resolve(txs), + getTransactionsByVersionsForRedeemedEvent: () => Promise.resolve(txs), getTransactions: () => Promise.resolve(txs), }; getSequenceNumberSpy = jest.spyOn(aptosRepo, "getSequenceNumber"); - getTransactionsForVersionsSpy = jest.spyOn(aptosRepo, "getTransactionsForVersions"); + getTransactionsByVersionsForSourceEventSpy = jest.spyOn( + aptosRepo, + "getTransactionsByVersionsForSourceEvent" + ); handlerSpy = jest.spyOn(handlers, "working"); }; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts new file mode 100644 index 000000000..2e7d93ab9 --- /dev/null +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -0,0 +1,392 @@ +import { + PollAptos, + PollAptosTransactionsConfig, + PollAptosTransactionsMetadata, +} from "../../../../src/domain/actions/aptos/PollAptos"; +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { + AptosRepository, + MetadataRepository, + StatRepository, +} from "../../../../src/domain/repositories"; +import { thenWaitForAssertion } from "../../../wait-assertion"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; + +let getTransactionsByVersionsForRedeemedEventSpy: jest.SpiedFunction< + AptosRepository["getTransactionsByVersionsForSourceEvent"] +>; +let getTransactionsSpy: jest.SpiedFunction; +let metadataSaveSpy: jest.SpiedFunction["save"]>; + +let handlerSpy: jest.SpiedFunction<(txs: TransactionsByVersion[]) => Promise>; + +let metadataRepo: MetadataRepository; +let aptosRepo: AptosRepository; +let statsRepo: StatRepository; + +let handlers = { + working: (txs: TransactionsByVersion[]) => Promise.resolve(), + failing: (txs: TransactionsByVersion[]) => Promise.reject(), +}; +let pollAptos: PollAptos; + +let props = { + blockBatchSize: 100, + fromBlock: 0n, + toBlock: 0n, + environment: "testnet", + commitment: "finalized", + addresses: ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], + interval: 5000, + topics: [], + chainId: 22, + filter: { + address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + type: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry", + }, + chain: "aptos", + id: "poll-log-message-published-aptos", +}; + +let cfg = new PollAptosTransactionsConfig(props); + +describe("GetAptosTransactions", () => { + afterEach(async () => { + await pollAptos.stop(); + }); + + it("should be not generate range (from and to block) and search the latest block plus block batch size cfg, and not process tx because is not a wormhole redeem", async () => { + // Given + const tx = { + version: "487572390", + hash: "0x487a4bfb6a7cda97090637ca5485afc3cb25e6eb21a873097ee3f0dcedc0b3b8", + state_change_hash: "0x40f10f464ce9301249fa44104c186a776a22b56cdffc94b6e3c4787d5d600538", + event_root_hash: "0x5017fa0a3016560a57eb8ed817ddbb0306d86c47fb4331ff993478c0acde30ca", + state_checkpoint_hash: null, + gas_used: "164", + success: true, + vm_status: "Executed successfully", + accumulator_root_hash: "0x26b6defda012418727f34c701fd9e65ed9e5f8e2e59aac7ebeb0ca7038a8d647", + changes: [], + sender: "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + sequence_number: "1494185", + max_gas_amount: "100000", + gas_unit_price: "100", + expiration_timestamp_secs: "1709822634", + payload: { + function: + "0xb1421c3d524a353411aa4e3cce0f0ce7f404a12da91a2889e1bc3cea6ffb17da::cancel_and_place::cancel_and_place", + type_arguments: [ + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::WETH", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC", + ], + arguments: [ + "8", + ["42645072269700382266349876", "42644869357063623703648693"], + ["42645090718696431883245683", "42644906249285249691670581"], + ["2517", "10041"], + ["383120", "383216"], + ["2539", "10008"], + ["382928", "382832"], + "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + 3, + 0, + ], + type: "entry_function_payload", + }, + signature: { + public_key: "0xc7756ecfa532b78c375a20e89910bf0120a9ec3431a02ed7e0e14999928d047d", + signature: + "0x09c4753e2efc67fd08e2060a97435e579f2157d95cc469568a0c9be804325cb4c8f1c1dc056763d866fa2eda5bf7b5b1ccbdce45aec1b77db79293bc855f5f02", + type: "ed25519_signature", + }, + events: [ + { + guid: { + creation_number: "10", + account_address: "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + }, + sequence_number: "2291689", + type: "0xc0deb00c405f84c85dc13442e305df75d1288100cdd82675695f6148c7ece51c::user::CancelOrderEvent", + data: { + custodian_id: "0", + market_id: "8", + order_id: "42645072269700382266349876", + reason: 3, + user: "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + }, + }, + ], + timestamp: "1709822034948509", + type: "user_transaction", + }; + + givenAptosBlockRepository(tx); + givenMetadataRepository(); + givenStatsRepository(); + givenPollAptosTx(cfg); + + // When + await whenPollEvmLogsStarts(); + + // Then + await thenWaitForAssertion( + () => expect(getTransactionsSpy).toHaveReturnedTimes(1), + () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: undefined, toBlock: 100 }) + ); + }); + + it("should be use fromBlock and batch size cfg, and process tx because is a wormhole redeem", async () => { + // Given + const tx = { + version: "487581688", + hash: "0x2853cb063b5351ea1b1ea46295bfadcd18117d20bc7b65de8db624284fd19061", + state_change_hash: "0x1513a95994c2b8319cef0bb728e56dcf51519cc5982d494541dbb91e7ba9ee2e", + event_root_hash: "0xadc25c39d0530da619a7620261194d6f3911aeed8c212dc3dfb699b2b6a07834", + state_checkpoint_hash: null, + gas_used: "753", + success: true, + vm_status: "Executed successfully", + accumulator_root_hash: "0x9b6e4552555f3584e13910c3a998159fef6c31568a23a9934832661f5bde5a09", + changes: [ + { + address: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + state_key_hash: "0xaf2393fef64599629efda83739a73fea2fc70c4d9bdff14e5681c396c51ab8f6", + data: { + type: "0x1::coin::CoinStore<0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea::coin::T>", + data: { + coin: { value: "524897921" }, + deposit_events: { + counter: "1291", + guid: { + id: { + addr: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + creation_num: "4", + }, + }, + }, + frozen: false, + withdraw_events: { + counter: "750", + guid: { + id: { + addr: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + creation_num: "5", + }, + }, + }, + }, + }, + type: "write_resource", + }, + { + address: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + state_key_hash: "0x18a0f4ffd938393773095ff40524b113a48e1c088fef980f202096402be6bd7b", + data: { + type: "0x1::account::Account", + data: { + authentication_key: + "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + coin_register_events: { + counter: "25", + guid: { + id: { + addr: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + creation_num: "0", + }, + }, + }, + guid_creation_num: "52", + key_rotation_events: { + counter: "0", + guid: { + id: { + addr: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + creation_num: "1", + }, + }, + }, + rotation_capability_offer: { for: { vec: [] } }, + sequence_number: "2050", + signer_capability_offer: { for: { vec: [] } }, + }, + }, + type: "write_resource", + }, + ], + sender: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + sequence_number: "2049", + max_gas_amount: "904", + gas_unit_price: "100", + expiration_timestamp_secs: "1709822592", + payload: { + function: + "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry", + type_arguments: [ + "0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea::coin::T", + ], + arguments: [ + "0x01000000030d0160b44a3ef503cf5a38ea72be662214c57b1431818e96fce55581085dc926379461f7942c11676bb9e992bf3dfca07c2ed1fc6d6df2dc50f1efee226cc66efae301039451110dcf560d2a79b8ba9aca679bcd56eba1c4db1c8f6f44c12973e394f2126b5e8944472b513b3196323bdddd31de64c20294dda6858b3ca77d0c8de940730004ffd7ac756815bce35c1e2cb72247ab4ab4ead25f4b072dc1c74937101b1e09cf04475447fbff003adb686338816d50bc3fedf988e733f03f8c11c359895b5a05000642879078f0c4964ed01bd46e977a032acc8aa0bf5140df04349c2fa843ee152a570b03286040702c59a40b9ddc8e99cd3d46c5529ce9f2d5cc3a3c0b7aafcd960007f910ed72608e482fce0b2afcc243dcb3e7b0f2e450215c8f056288e631abc8966467f976eb39c992f20ef61b47b3587f0566fc934880357ad019da98ba29768c00088aa7b98f820581e011a5e554d347f5d714e16a74674d6117e3772ee77b20ca30157144b31ec203e18c8815f3c21feca7e549d5111e48c2f7d9362139abeb6bd1010989e8c573acb24be1915f0152f790d53c195ee8850eb7e41988e6e019e4a6decf390d19a16ae161b65ef265ff75a01b3ad23fe9e99d124e140a6d64fd8808d738010a9aa6b777d25f2bf4500f6ce574617350a136e217c2bfe8ba97a62a6cf3375eff158fb81149ae439f0f43ae7727189a8f6b5557318caa35829484ba1f5abb3eb8000c12fc58d0e3cfa020f126495e5d3f9401ac82dc48d6af1a22d8950b9cdb5fd8370181d4ca59ec41cc193bbb39618b1b645d557999ef97e89c919cec99aabe42c7010daa874ee1b0d6f3462c8f83d108953eb80d797d908d3c121c46e8a5514f50f2470822076d8f59e2934c4ee365520c53f9ea9ee3014d0ed8adc85c3653a0ac77000010e31607f6bfd58aa3d9c423b410c1081cbef7ee215a6a412c12ba60807867bcbd680d82a5ecf92ac99bb0682e670c38415304ed6eb9e7c95b0095866fdd2a67a100115e20281a08ebb632a9ff584f11924dd62edcaaceb89123814ce3598bd231b6d8678c13876a1f7c0b8c07acb7e0fad4d002f839db2f6f84b5f2ddfdc4ba3930f001126414cd214b9d5e0e0dbb2fad61ee9d567ffb79b7a575d84acd0bc8dbd28ea15f74feca54a47c5adae94b1718556b44f88a7993dc78602b9907738b0bd6427dcd0165e9d1e4400b01000010000000000000000000000000b1731c586ca89a23809861c6103f0b96b3f57d92000000000001219e0101000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480002a9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd200160000000000000000000000000000000000000000000000000000000000000000", + ], + type: "entry_function_payload", + }, + signature: { + public_key: "0x5d4d25db389615c6ba75d3920690a8dfbe2108e21835668ebdee9d2096b7e104", + signature: + "0xd849b7400247c747268ccc30a6879995d8792e83116cccbe53ba564bd0517f0c82299302014fd7bd089e6e00090b0d9ad46c8f2ecd1bd81ef2d6d0ae094a870f", + type: "ed25519_signature", + }, + events: [ + { + guid: { + creation_number: "4", + account_address: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + }, + sequence_number: "1289", + type: "0x1::coin::DepositEvent", + data: { amount: "500000000" }, + }, + { + guid: { + creation_number: "4", + account_address: "0xa9e33cfc7bb1f0d8fc63dcfbfecaff4806facfee290c284cd83ae10763ca0bd2", + }, + sequence_number: "1290", + type: "0x1::coin::DepositEvent", + data: { amount: "0" }, + }, + { + guid: { creation_number: "0", account_address: "0x0" }, + sequence_number: "0", + type: "0x1::transaction_fee::FeeStatement", + data: { + execution_gas_units: "118", + io_gas_units: "8", + storage_fee_octas: "62820", + storage_fee_refund_octas: "0", + total_charge_gas_units: "753", + }, + }, + ], + timestamp: "1709822474112433", + type: "user_transaction", + }; + givenAptosBlockRepository(tx); + givenMetadataRepository(); + givenStatsRepository(); + // Se fromBlock for cfg + props.fromBlock = 146040n; + givenPollAptosTx(cfg); + + // Whem + await whenPollEvmLogsStarts(); + + // Then + await thenWaitForAssertion( + () => expect(getTransactionsSpy).toHaveReturnedTimes(1), + () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }), + () => expect(getTransactionsByVersionsForRedeemedEventSpy).toHaveReturnedTimes(1) + ); + }); + + it("should be return the same last block and the to block equal 100", async () => { + // Given + givenAptosBlockRepository(); + givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); + givenStatsRepository(); + // Se fromBlock for cfg + props.fromBlock = 0n; + givenPollAptosTx(cfg); + + // Whem + await whenPollEvmLogsStarts(); + + // Then + await thenWaitForAssertion( + () => expect(getTransactionsSpy).toHaveReturnedTimes(1), + () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }) + ); + }); + + it("should be if return the last block and the to block equal the block batch size", async () => { + // Given + givenAptosBlockRepository(); + givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); + givenStatsRepository(); + // Se fromBlock for cfg + props.fromBlock = 0n; + givenPollAptosTx(cfg); + + // Whem + await whenPollEvmLogsStarts(); + + // Then + await thenWaitForAssertion( + () => expect(getTransactionsSpy).toHaveReturnedTimes(1), + () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }) + ); + }); +}); + +const givenAptosBlockRepository = (tx: any = {}) => { + const events = [ + { + version: "481740133", + guid: { + creation_number: "2", + account_address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + }, + sequence_number: "148985", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", + data: { + consistency_level: 0, + nonce: "41611", + payload: + "0x0100000000000000000000000000000000000000000000000000000000003b826000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab10017000000000000000000000000451febd0f01b9d6bda1a5b9d0b6ef88026e4a79100170000000000000000000000000000000000000000000000000000000000011c1e", + sender: "1", + sequence: "146040", + timestamp: "1709585379", + }, + }, + ]; + + const txs = [tx]; + + aptosRepo = { + getSequenceNumber: () => Promise.resolve(events), + getTransactionsByVersionsForSourceEvent: () => Promise.resolve([]), + getTransactionsByVersionsForRedeemedEvent: () => Promise.resolve([]), + getTransactions: () => Promise.resolve(txs), + }; + + getTransactionsSpy = jest.spyOn(aptosRepo, "getTransactions"); + getTransactionsByVersionsForRedeemedEventSpy = jest.spyOn( + aptosRepo, + "getTransactionsByVersionsForRedeemedEvent" + ); + handlerSpy = jest.spyOn(handlers, "working"); +}; + +const givenMetadataRepository = (data?: PollAptosTransactionsMetadata) => { + metadataRepo = { + get: () => Promise.resolve(data), + save: () => Promise.resolve(), + }; + metadataSaveSpy = jest.spyOn(metadataRepo, "save"); +}; + +const givenStatsRepository = () => { + statsRepo = { + count: () => {}, + measure: () => {}, + report: () => Promise.resolve(""), + }; +}; + +const givenPollAptosTx = (cfg: PollAptosTransactionsConfig) => { + pollAptos = new PollAptos(cfg, statsRepo, metadataRepo, aptosRepo, "GetAptosTransactions"); +}; + +const whenPollEvmLogsStarts = async () => { + pollAptos.run([handlers.working]); +}; diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index e3eb96678..bd9d0f97c 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -18,12 +18,15 @@ describe("HandleAptosTransactions", () => { afterEach(async () => {}); it("should be able to map source events tx", async () => { + // Given givenConfig(); givenStatsRepository(); givenHandleEvmLogs(); + // When const result = await handleAptosTransactions.handle(txs); + // Then expect(result).toHaveLength(1); expect(result[0].name).toBe("log-message-published"); expect(result[0].chainId).toBe(22); @@ -101,7 +104,7 @@ txs = [ blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, - sequence: "34", + sequence: 3423n, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", @@ -166,7 +169,7 @@ txs = [ }, }, ], - nonce: "76704", + nonce: 76704, hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", }, ]; diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index b8904d560..5c77587be 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -1,11 +1,14 @@ -import { describe, it, expect } from "@jest/globals"; import { aptosLogMessagePublishedMapper } from "../../../../src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper"; import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { describe, it, expect } from "@jest/globals"; describe("aptosLogMessagePublishedMapper", () => { it("should be able to map log to aptosLogMessagePublishedMapper", async () => { + // When const result = aptosLogMessagePublishedMapper(txs); + if (result) { + // Then expect(result.name).toBe("log-message-published"); expect(result.chainId).toBe(22); expect(result.txHash).toBe( @@ -22,7 +25,7 @@ describe("aptosLogMessagePublishedMapper", () => { expect(result.attributes.sender).toBe( "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2" ); - expect(result.attributes.sequence).toBe(34); + expect(result.attributes.sequence).toBe(3423); } }); }); @@ -32,7 +35,7 @@ const txs: TransactionsByVersion = { blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, - sequence: "34", + sequence: 3423n, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", @@ -97,6 +100,6 @@ const txs: TransactionsByVersion = { }, }, ], - nonce: "76704", + nonce: 76704, hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", }; diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts new file mode 100644 index 000000000..0126ab3a9 --- /dev/null +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts @@ -0,0 +1,109 @@ +import { aptosRedeemedTransactionFoundMapper } from "../../../../src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { describe, it, expect } from "@jest/globals"; + +describe("aptosRedeemedTransactionFoundMapper", () => { + it("should be able to map log to aptosRedeemedTransactionFoundMapper", async () => { + // When + const result = aptosRedeemedTransactionFoundMapper(tx); + + if (result) { + // Then + expect(result.name).toBe("transfer-redeemed"); + expect(result.chainId).toBe(22); + expect(result.txHash).toBe( + "0xd297f46372ed734fd8f3b595a898f42ab328a4e3cc9ce0f810c6c66e64873e64" + ); + expect(result.address).toBe( + "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f" + ); + expect(result.attributes.from).toBe( + "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" + ); + expect(result.attributes.emitterChain).toBe(5); + expect(result.attributes.emitterAddress).toBe( + "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" + ); + expect(result.attributes.sequence).toBe(394768); + expect(result.attributes.status).toBe("success"); + expect(result.attributes.protocol).toBe("Token Bridge"); + } + }); + + it("should not be able to map log to aptosRedeemedTransactionFoundMapper", async () => { + // Given + const tx: TransactionsByVersion = { + consistencyLevel: 15, + emitterChain: 5, + blockHeight: 154363203n, + timestamp: 1709821894, + blockTime: 1709821894, + sequence: 394768n, + version: "487572005", + payload: + "010000000000000000000000000000000000000000000000000000000000989680069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f0000000000100019cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea00160000000000000000000000000000000000000000000000000000000000000000", + address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", + sender: "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde", + status: true, + events: [], + nonce: 302448640, + hash: "0xd297f46372ed734fd8f3b595a898f42ab328a4e3cc9ce0f810c6c66e64873e64", + type: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::cancel", + }; + // When + const result = aptosRedeemedTransactionFoundMapper(tx); + + // Then + expect(result).toBeUndefined(); + }); +}); + +const tx: TransactionsByVersion = { + consistencyLevel: 15, + emitterChain: 5, + blockHeight: 154363203n, + timestamp: 1709821894, + blockTime: 1709821894, + sequence: 394768n, + version: "487572005", + payload: + "010000000000000000000000000000000000000000000000000000000000989680069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f0000000000100019cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea00160000000000000000000000000000000000000000000000000000000000000000", + address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", + sender: "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde", + status: true, + events: [ + { + guid: { + creation_number: "4", + account_address: "0x9cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea", + }, + sequence_number: "4", + type: "0x1::coin::DepositEvent", + data: { amount: "10000000" }, + }, + { + guid: { + creation_number: "4", + account_address: "0x9cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea", + }, + sequence_number: "5", + type: "0x1::coin::DepositEvent", + data: { amount: "0" }, + }, + { + guid: { creation_number: "0", account_address: "0x0" }, + sequence_number: "0", + type: "0x1::transaction_fee::FeeStatement", + data: { + execution_gas_units: "118", + io_gas_units: "8", + storage_fee_octas: "62820", + storage_fee_refund_octas: "0", + total_charge_gas_units: "753", + }, + }, + ], + nonce: 302448640, + hash: "0xd297f46372ed734fd8f3b595a898f42ab328a4e3cc9ce0f810c6c66e64873e64", + type: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry", +}; diff --git a/deploy/blockchain-watcher/workers/source-events-1.yaml b/deploy/blockchain-watcher/workers/source-events-1.yaml index ae5796d68..eae90609d 100644 --- a/deploy/blockchain-watcher/workers/source-events-1.yaml +++ b/deploy/blockchain-watcher/workers/source-events-1.yaml @@ -367,7 +367,7 @@ data: "metricLabels": { "job": "poll-log-message-published-aptos", "chain": "aptos", - "commitment": "immediate" + "commitment": "finalized" } } } @@ -617,7 +617,7 @@ data: "metricLabels": { "job": "poll-log-message-published-aptos", "chain": "aptos", - "commitment": "immediate" + "commitment": "finalized" } } } diff --git a/deploy/blockchain-watcher/workers/target-events-1.yaml b/deploy/blockchain-watcher/workers/target-events-1.yaml index 4126f069d..0790abec9 100644 --- a/deploy/blockchain-watcher/workers/target-events-1.yaml +++ b/deploy/blockchain-watcher/workers/target-events-1.yaml @@ -37,31 +37,30 @@ data: testnet-jobs.json: |- [ { - "id": "poll-log-message-published-aptos", + "id": "poll-redeemed-transactions-aptos", "chain": "aptos", "source": { "action": "PollAptos", + "records": "GetAptosTransactions", "config": { - "blockBatchSize": 100, + "blockBatchSize": 1000, "commitment": "finalized", "interval": 5000, "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], "chain": "aptos", "chainId": 22, "filter": { - "fieldName": "event", "address": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", - "event": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" + "type": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" } } }, "handlers": [ { "action": "HandleAptosTransactions", - "target": "dummy", + "target": "sns", "mapper": "aptosRedeemedTransactionFoundMapper", "config": { - "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", "metricName": "process_source_event" } } @@ -71,36 +70,35 @@ data: mainnet-jobs.json: |- [ { - "id": "poll-log-message-published-aptos", + "id": "poll-redeemed-transactions-aptos", "chain": "aptos", "source": { "action": "PollAptos", + "records": "GetAptosTransactions", "config": { - "blockBatchSize": 100, + "blockBatchSize": 1000, "commitment": "finalized", "interval": 5000, "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], "chain": "aptos", "chainId": 22, "filter": { - "fieldName": "event", "address": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", - "event": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" + "type": "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry" } } }, "handlers": [ { "action": "HandleAptosTransactions", - "target": "dummy", + "target": "sns", "mapper": "aptosRedeemedTransactionFoundMapper", "config": { - "abi": "event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel)", "metricName": "process_source_event" } } ] - } + } ] --- apiVersion: apps/v1 diff --git a/deploy/blockchain-watcher/workers/target-events.yaml b/deploy/blockchain-watcher/workers/target-events.yaml index cd039c746..95ed4a361 100644 --- a/deploy/blockchain-watcher/workers/target-events.yaml +++ b/deploy/blockchain-watcher/workers/target-events.yaml @@ -494,7 +494,7 @@ data: "config": { "blockBatchSize": 100, "commitment": "latest", - "interval": 10000, + "interval": 5000, "addresses": ["0x4cb69fae7e7af841e44e1a1c30af640739378bb2", "0x19330d10D9Cc8751218eaf51E8885D058642E08A", "0x0b2402144bb366a632d14b83f244d2e0e21bd39c", "0x48fa7528bfd6164ddf09df0ed22451cf59c84130", "0xf3f04555f8fda510bfc77820fd6eb8446f59e72d"], "chain": "arbitrum", "chainId": 23, From 288a199a0b9c9fddc2152353f367da10364f0a78 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Fri, 8 Mar 2024 09:58:46 -0300 Subject: [PATCH 10/30] Remove hardcode params --- .../src/infrastructure/rpc/http/InstrumentedAptosProvider.ts | 2 +- deploy/blockchain-watcher/workers/target-events.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index f5cd0b46b..d76311874 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -81,7 +81,7 @@ export class InstrumentedAptosProvider { public async getTransactions(block: Block): Promise { try { - const params = 1 + const params = block.fromBlock ? { start: block.fromBlock, limit: block.toBlock } : { limit: block.toBlock }; diff --git a/deploy/blockchain-watcher/workers/target-events.yaml b/deploy/blockchain-watcher/workers/target-events.yaml index 95ed4a361..cd039c746 100644 --- a/deploy/blockchain-watcher/workers/target-events.yaml +++ b/deploy/blockchain-watcher/workers/target-events.yaml @@ -494,7 +494,7 @@ data: "config": { "blockBatchSize": 100, "commitment": "latest", - "interval": 5000, + "interval": 10000, "addresses": ["0x4cb69fae7e7af841e44e1a1c30af640739378bb2", "0x19330d10D9Cc8751218eaf51E8885D058642E08A", "0x0b2402144bb366a632d14b83f244d2e0e21bd39c", "0x48fa7528bfd6164ddf09df0ed22451cf59c84130", "0xf3f04555f8fda510bfc77820fd6eb8446f59e72d"], "chain": "arbitrum", "chainId": 23, From 8664c2e6f165dbc60272d535270088b20201eaf0 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Fri, 8 Mar 2024 15:48:33 -0300 Subject: [PATCH 11/30] Change completed status --- blockchain-watcher/src/domain/entities/aptos.ts | 2 +- .../mappers/aptos/aptosRedeemedTransactionFoundMapper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 0a1924d8c..4fcc286bf 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -13,6 +13,6 @@ export type AptosEvent = Omit & { }; export enum TxStatus { - Confirmed = "success", + Completed = "completed", Failed = "failed", } diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index f70f3324b..595c710b1 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -33,7 +33,7 @@ export const aptosRedeemedTransactionFoundMapper = ( emitterChain: tx.emitterChain, emitterAddress: emitterAddress, sequence: Number(tx.sequence), - status: tx?.status === true ? TxStatus.Confirmed : TxStatus.Failed, + status: tx.status === true ? TxStatus.Completed : TxStatus.Failed, protocol: protocol.method, }, }; From 9079fd4160655d5a6817af199eaebd08d6f14db7 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Fri, 8 Mar 2024 15:56:53 -0300 Subject: [PATCH 12/30] Merge to main --- .../mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts index 0126ab3a9..ae5c0046d 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts @@ -25,7 +25,7 @@ describe("aptosRedeemedTransactionFoundMapper", () => { "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" ); expect(result.attributes.sequence).toBe(394768); - expect(result.attributes.status).toBe("success"); + expect(result.attributes.status).toBe("completed"); expect(result.attributes.protocol).toBe("Token Bridge"); } }); From bf65ae8a6f28ac6d3dfe1c15dbadd0a0d7104235 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Fri, 8 Mar 2024 17:31:01 -0300 Subject: [PATCH 13/30] Improve code style --- .../domain/actions/aptos/GetAptosSequences.ts | 51 +++++++------------ .../actions/aptos/GetAptosTransactions.ts | 39 ++++---------- .../actions/aptos/HandleAptosTransactions.ts | 5 -- .../src/domain/actions/aptos/PollAptos.ts | 2 +- .../src/domain/entities/aptos.ts | 5 -- blockchain-watcher/src/domain/repositories.ts | 8 +-- .../aptosRedeemedTransactionFoundMapper.ts | 16 +++--- .../aptos/AptosJsonRPCBlockRepository.ts | 12 ++--- .../RateLimitedAptosJsonRPCBlockRepository.ts | 8 +-- .../repositories/common/utils.ts | 21 ++++++++ .../actions/aptos/GetAptosSequences.test.ts | 26 +++++----- .../aptos/GetAptosTransactions.test.ts | 26 +++++----- .../aptos/HandleAptosTransactions.test.ts | 4 +- .../workers/source-events.yaml | 2 +- ...get-events-1.yaml => target-events-2.yaml} | 26 +++++----- 15 files changed, 117 insertions(+), 134 deletions(-) rename deploy/blockchain-watcher/workers/{target-events-1.yaml => target-events-2.yaml} (91%) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index c6c2409e8..c1a99c0a6 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -1,14 +1,15 @@ -import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { Block, TransactionFilter } from "./PollAptos"; +import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { AptosRepository } from "../../repositories"; +import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; export class GetAptosSequences { protected readonly logger: winston.Logger; private readonly repo: AptosRepository; - private lastBlock?: bigint; private previousBlock?: bigint; + private lastBlock?: bigint; constructor(repo: AptosRepository) { this.logger = winston.child({ module: "GetAptosSequences" }); @@ -19,34 +20,37 @@ export class GetAptosSequences { let populatedTransactions: TransactionsByVersion[] = []; this.logger.info( - `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` + `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - lastBlock: ${opts.lastBlock}]` ); - const batches = this.createBatches(range); + const batches = createBatches(range); + + for (const toBatch of batches) { + const fromBatch = this.lastBlock ? Number(this.lastBlock) : range?.fromBlock; - for (const batch of batches) { const events = await this.repo.getSequenceNumber( { - fromBlock: range?.fromBlock, - toBlock: batch, + fromBlock: fromBatch, + toBlock: toBatch, }, opts.filter ); - // update last block with the new block + // update lastBlock with the new lastBlock this.lastBlock = BigInt(events[events.length - 1].sequence_number); if (opts.previousBlock == this.lastBlock) { return []; } - // save previous block with last block + // update previousBlock with opts lastBlock this.previousBlock = opts.lastBlock; - const transactions = await this.repo.getTransactionsByVersionsForSourceEvent( + const transactions = await this.repo.getTransactionsByVersionForSourceEvent( events, opts.filter ); + transactions.forEach((tx) => { populatedTransactions.push(tx); }); @@ -64,7 +68,7 @@ export class GetAptosSequences { savedPreviousSequence: bigint | undefined, savedLastBlock: bigint | undefined ): Block | undefined { - // if [set up a from block for cfg], return the from block and the to block equal the block batch size + // if [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size if (cfgFromBlock) { return { fromBlock: Number(cfgFromBlock), @@ -73,7 +77,7 @@ export class GetAptosSequences { } if (savedPreviousSequence && savedLastBlock) { - // if process the [same block], return the same last block and the to block equal the block batch size + // if process the [same block], return the same lastBlock and toBlock equal the block batch size if (savedPreviousSequence === savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -81,7 +85,7 @@ export class GetAptosSequences { }; } - // if process [different sequences], return the difference between the last block and the previous block plus 1 + // if process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 if (savedPreviousSequence !== savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -91,7 +95,7 @@ export class GetAptosSequences { } if (savedLastBlock) { - // if there is [no previous block], return the last block and the to block equal the block batch size + // if there is [no previous block], return the lastBlock and toBlock equal the block batch size if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -107,25 +111,6 @@ export class GetAptosSequences { lastBlock: this.lastBlock, }; } - - private createBatches(range: Block | undefined): number[] { - let batchSize = 100; - let total = 1; - - if (range && range.toBlock) { - batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; - total = range.toBlock ?? total; - } - - const numBatches = Math.ceil(total / batchSize); - const batches: number[] = []; - - for (let i = 0; i < numBatches; i++) { - batches.push(batchSize); - } - - return batches; - } } export type GetAptosOpts = { diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 666efd705..27f46b8b7 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -1,14 +1,15 @@ import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { Block, TransactionFilter } from "./PollAptos"; import { AptosRepository } from "../../repositories"; +import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; -import { Block, TransactionFilter } from "./PollAptos"; export class GetAptosTransactions { private readonly repo: AptosRepository; protected readonly logger: winston.Logger; - private lastBlock?: bigint; private previousBlock?: bigint; + private lastBlock?: bigint; constructor(repo: AptosRepository) { this.logger = winston.child({ module: "GetAptosTransactions" }); @@ -22,7 +23,7 @@ export class GetAptosTransactions { `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` ); - const batches = this.createBatches(range); + const batches = createBatches(range); for (const toBatch of batches) { const fromBatch = this.lastBlock ? Number(this.lastBlock) : range?.fromBlock; @@ -37,21 +38,22 @@ export class GetAptosTransactions { opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) ); - // update last block with the new block + // update lastBlock with the new lastBlock this.lastBlock = BigInt(transaction[transaction.length - 1].version); if (opts.previousBlock == this.lastBlock) { return []; } - // save previous block with last block + // update previousBlock with opts lastBlock this.previousBlock = opts.lastBlock; if (transactionsByAddressConfigured.length > 0) { - const transactions = await this.repo.getTransactionsByVersionsForRedeemedEvent( + const transactions = await this.repo.getTransactionsByVersionForRedeemedEvent( transactionsByAddressConfigured, opts.filter ); + transactions.forEach((tx) => { populatedTransactions.push(tx); }); @@ -67,7 +69,7 @@ export class GetAptosTransactions { savedPreviousBlock: bigint | undefined, savedLastBlock: bigint | undefined ): Block | undefined { - // if [set up a from block for cfg], return the from block and the to block equal the block batch size + // if [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size if (cfgFromBlock) { return { fromBlock: Number(cfgFromBlock), @@ -76,7 +78,7 @@ export class GetAptosTransactions { } if (savedPreviousBlock && savedLastBlock) { - // if process [equal or different blocks], return the same last block and the to block equal the block batch size + // if process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -86,7 +88,7 @@ export class GetAptosTransactions { } if (savedLastBlock) { - // if there is [no previous block], return the last block and the to block equal the block batch size + // if there is [no previous block], return the lastBlock and toBlock equal the block batch size if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -102,25 +104,6 @@ export class GetAptosTransactions { lastBlock: this.lastBlock, }; } - - private createBatches(range: Block | undefined): number[] { - let batchSize = 100; - let total = 1; - - if (range && range.toBlock) { - batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; - total = range.toBlock ?? total; - } - - const numBatches = Math.ceil(total / batchSize); - const batches: number[] = []; - - for (let i = 0; i < numBatches; i++) { - batches.push(batchSize); - } - - return batches; - } } export type GetAptosOpts = { diff --git a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts index 80afadcbb..c03876b63 100644 --- a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts @@ -1,9 +1,6 @@ import { TransactionFoundEvent } from "../../entities"; import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { StatRepository } from "../../repositories"; -import winston from "winston"; - -let logger: winston.Logger = winston.child({ module: "HandleAptosTransactions" }); export class HandleAptosTransactions { constructor( @@ -37,8 +34,6 @@ export class HandleAptosTransactions { protocol: protocol, }; - logger.debug(`[aptos] Build labels: [labels: ${JSON.stringify(labels)}]`); - this.statsRepo.count(this.cfg.metricName, labels); } } diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index cb83ec4b6..dc39840c1 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -117,7 +117,7 @@ export class PollAptosTransactionsConfig { } public getCommitment() { - return this.props.commitment ?? "latest"; + return this.props.commitment ?? "finalized"; } public get id(): string { diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 4fcc286bf..f32a487a3 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -11,8 +11,3 @@ export type AptosEvent = Omit & { timestamp: string; }; }; - -export enum TxStatus { - Completed = "completed", - Failed = "failed", -} diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index cd2c5e540..b9def79c6 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -18,9 +18,9 @@ import { import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; -import { AptosEvent } from "./entities/aptos"; -import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { Block, TransactionFilter } from "./actions/aptos/PollAptos"; +import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosEvent } from "./entities/aptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -73,11 +73,11 @@ export interface SuiRepository { export interface AptosRepository { getTransactions(range: Block | undefined): Promise; getSequenceNumber(range: Block | undefined, filter: TransactionFilter): Promise; - getTransactionsByVersionsForSourceEvent( + getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter ): Promise; - getTransactionsByVersionsForRedeemedEvent( + getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter ): Promise; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index 595c710b1..d8ed10062 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -1,8 +1,7 @@ import { TransactionFoundEvent } from "../../../domain/entities"; import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; -import { findProtocol } from "../contractsMapper"; import { CHAIN_ID_APTOS } from "@certusone/wormhole-sdk"; -import { TxStatus } from "../../../domain/entities/aptos"; +import { findProtocol } from "../contractsMapper"; import winston from "winston"; let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFoundMapper" }); @@ -16,11 +15,11 @@ export const aptosRedeemedTransactionFoundMapper = ( const protocol = findProtocol(APTOS_CHAIN, tx.address, tx.type!, tx.hash); - logger.info( - `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${tx.emitterChain}/${emitterAddress}/${tx.sequence}]` - ); - if (protocol && protocol.type && protocol.method) { + logger.info( + `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${tx.emitterChain}/${emitterAddress}/${tx.sequence}]` + ); + return { name: "transfer-redeemed", address: tx.address, @@ -39,3 +38,8 @@ export const aptosRedeemedTransactionFoundMapper = ( }; } }; + +enum TxStatus { + Completed = "completed", + Failed = "failed", +} diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 0232ed858..79832ae96 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,7 +1,7 @@ -import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; -import { parseVaa } from "@certusone/wormhole-sdk"; +import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { AptosEvent } from "../../../domain/entities/aptos"; +import { parseVaa } from "@certusone/wormhole-sdk"; import winston from "winston"; export class AptosJsonRPCBlockRepository { @@ -33,7 +33,7 @@ export class AptosJsonRPCBlockRepository { } } - async getTransactionsByVersionsForSourceEvent( + async getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { @@ -65,12 +65,12 @@ export class AptosJsonRPCBlockRepository { return transactions; } catch (e) { - this.handleError(e, "getTransactionsForVersions"); + this.handleError(e, "getTransactionsByVersionForSourceEvent"); throw e; } } - async getTransactionsByVersionsForRedeemedEvent( + async getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { @@ -105,7 +105,7 @@ export class AptosJsonRPCBlockRepository { return transactions; } catch (e) { - this.handleError(e, "getTransactionsForVersions"); + this.handleError(e, "getTransactionsByVersionForRedeemedEvent"); throw e; } } diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index 9dc0d1ac9..7a02f2e19 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -19,21 +19,21 @@ export class RateLimitedAptosJsonRPCBlockRepository return this.breaker.fn(() => this.delegate.getSequenceNumber(range, filter)).execute(); } - getTransactionsByVersionsForSourceEvent( + getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { return this.breaker - .fn(() => this.delegate.getTransactionsByVersionsForSourceEvent(events, filter)) + .fn(() => this.delegate.getTransactionsByVersionForSourceEvent(events, filter)) .execute(); } - getTransactionsByVersionsForRedeemedEvent( + getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter ): Promise { return this.breaker - .fn(() => this.delegate.getTransactionsByVersionsForRedeemedEvent(events, filter)) + .fn(() => this.delegate.getTransactionsByVersionForRedeemedEvent(events, filter)) .execute(); } diff --git a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts index 780735681..fb70ac2a5 100644 --- a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts +++ b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts @@ -1,3 +1,5 @@ +import { Block } from "../../../domain/actions/aptos/PollAptos"; + export function divideIntoBatches(set: Set, batchSize = 10): Set[] { const batches: Set[] = []; let batch: any[] = []; @@ -15,3 +17,22 @@ export function divideIntoBatches(set: Set, batchSize = 10): Set[] { } return batches; } + +export function createBatches(range: Block | undefined): number[] { + let batchSize = 100; + let total = 1; + + if (range && range.toBlock) { + batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; + total = range.toBlock ?? total; + } + + const numBatches = Math.ceil(total / batchSize); + const batches: number[] = []; + + for (let i = 0; i < numBatches; i++) { + batches.push(batchSize); + } + + return batches; +} diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index d232a9565..3dab3e734 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -1,19 +1,19 @@ +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { thenWaitForAssertion } from "../../../wait-assertion"; import { - PollAptos, - PollAptosTransactionsConfig, PollAptosTransactionsMetadata, + PollAptosTransactionsConfig, + PollAptos, } from "../../../../src/domain/actions/aptos/PollAptos"; -import { afterEach, describe, it, expect, jest } from "@jest/globals"; import { - AptosRepository, MetadataRepository, + AptosRepository, StatRepository, } from "../../../../src/domain/repositories"; -import { thenWaitForAssertion } from "../../../wait-assertion"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; let getTransactionsByVersionsForSourceEventSpy: jest.SpiedFunction< - AptosRepository["getTransactionsByVersionsForSourceEvent"] + AptosRepository["getTransactionsByVersionForSourceEvent"] >; let getSequenceNumberSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; @@ -58,7 +58,7 @@ describe("GetAptosSequences", () => { await pollAptos.stop(); }); - it("should be not generate range (from and to block) and search the latest block plus block batch size cfg", async () => { + it("should be not generate range (fromBlock and toBlock) and search the latest block plus block batch size cfg", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository(); @@ -116,7 +116,7 @@ describe("GetAptosSequences", () => { ); }); - it("should be return the same last block and the to block equal 100", async () => { + it("should be return the same lastBlock and toBlock equal 100", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); @@ -146,7 +146,7 @@ describe("GetAptosSequences", () => { ); }); - it("should be if return the last block and the to block equal the block batch size", async () => { + it("should be if return the lastBlock and toBlock equal the block batch size", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); @@ -277,15 +277,15 @@ const givenAptosBlockRepository = () => { aptosRepo = { getSequenceNumber: () => Promise.resolve(events), - getTransactionsByVersionsForSourceEvent: () => Promise.resolve(txs), - getTransactionsByVersionsForRedeemedEvent: () => Promise.resolve(txs), + getTransactionsByVersionForSourceEvent: () => Promise.resolve(txs), + getTransactionsByVersionForRedeemedEvent: () => Promise.resolve(txs), getTransactions: () => Promise.resolve(txs), }; getSequenceNumberSpy = jest.spyOn(aptosRepo, "getSequenceNumber"); getTransactionsByVersionsForSourceEventSpy = jest.spyOn( aptosRepo, - "getTransactionsByVersionsForSourceEvent" + "getTransactionsByVersionForSourceEvent" ); handlerSpy = jest.spyOn(handlers, "working"); }; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index 2e7d93ab9..23a3c5f68 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -1,19 +1,19 @@ +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { thenWaitForAssertion } from "../../../wait-assertion"; import { - PollAptos, - PollAptosTransactionsConfig, PollAptosTransactionsMetadata, + PollAptosTransactionsConfig, + PollAptos, } from "../../../../src/domain/actions/aptos/PollAptos"; -import { afterEach, describe, it, expect, jest } from "@jest/globals"; import { - AptosRepository, MetadataRepository, + AptosRepository, StatRepository, } from "../../../../src/domain/repositories"; -import { thenWaitForAssertion } from "../../../wait-assertion"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; let getTransactionsByVersionsForRedeemedEventSpy: jest.SpiedFunction< - AptosRepository["getTransactionsByVersionsForSourceEvent"] + AptosRepository["getTransactionsByVersionForSourceEvent"] >; let getTransactionsSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; @@ -55,7 +55,7 @@ describe("GetAptosTransactions", () => { await pollAptos.stop(); }); - it("should be not generate range (from and to block) and search the latest block plus block batch size cfg, and not process tx because is not a wormhole redeem", async () => { + it("should be not generate range (fromBlock and toBlock) and search the latest block plus block batch size cfg, and not process tx because is not a wormhole redeem", async () => { // Given const tx = { version: "487572390", @@ -289,7 +289,7 @@ describe("GetAptosTransactions", () => { ); }); - it("should be return the same last block and the to block equal 100", async () => { + it("should be return the same lastBlock and toBlock equal 100", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); @@ -308,7 +308,7 @@ describe("GetAptosTransactions", () => { ); }); - it("should be if return the last block and the to block equal the block batch size", async () => { + it("should be if return the lastBlock and toBlock equal the block batch size", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); @@ -354,15 +354,15 @@ const givenAptosBlockRepository = (tx: any = {}) => { aptosRepo = { getSequenceNumber: () => Promise.resolve(events), - getTransactionsByVersionsForSourceEvent: () => Promise.resolve([]), - getTransactionsByVersionsForRedeemedEvent: () => Promise.resolve([]), + getTransactionsByVersionForSourceEvent: () => Promise.resolve([]), + getTransactionsByVersionForRedeemedEvent: () => Promise.resolve([]), getTransactions: () => Promise.resolve(txs), }; getTransactionsSpy = jest.spyOn(aptosRepo, "getTransactions"); getTransactionsByVersionsForRedeemedEventSpy = jest.spyOn( aptosRepo, - "getTransactionsByVersionsForRedeemedEvent" + "getTransactionsByVersionForRedeemedEvent" ); handlerSpy = jest.spyOn(handlers, "working"); }; diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index bd9d0f97c..052e8dc62 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -1,10 +1,10 @@ import { afterEach, describe, it, expect, jest } from "@jest/globals"; -import { LogFoundEvent, LogMessagePublished } from "../../../../src/domain/entities"; import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { StatRepository } from "../../../../src/domain/repositories"; +import { LogFoundEvent } from "../../../../src/domain/entities"; import { - HandleAptosTransactions, HandleAptosTransactionsOptions, + HandleAptosTransactions, } from "../../../../src/domain/actions/aptos/HandleAptosTransactions"; let targetRepoSpy: jest.SpiedFunction<(typeof targetRepo)["save"]>; diff --git a/deploy/blockchain-watcher/workers/source-events.yaml b/deploy/blockchain-watcher/workers/source-events.yaml index 731ec925f..4113bfc47 100644 --- a/deploy/blockchain-watcher/workers/source-events.yaml +++ b/deploy/blockchain-watcher/workers/source-events.yaml @@ -247,7 +247,7 @@ data: "source": { "action": "PollEvm", "config": { - "blockBatchSize": 50, + "blockBatchSize": 100, "commitment": "latest", "interval": 15000, "addresses": ["0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D"], diff --git a/deploy/blockchain-watcher/workers/target-events-1.yaml b/deploy/blockchain-watcher/workers/target-events-2.yaml similarity index 91% rename from deploy/blockchain-watcher/workers/target-events-1.yaml rename to deploy/blockchain-watcher/workers/target-events-2.yaml index 0790abec9..e0058c76d 100644 --- a/deploy/blockchain-watcher/workers/target-events-1.yaml +++ b/deploy/blockchain-watcher/workers/target-events-2.yaml @@ -2,23 +2,23 @@ apiVersion: v1 kind: Service metadata: - name: {{ .NAME }}-target-events-1 + name: {{ .NAME }}-target-events-2 namespace: {{ .NAMESPACE }} labels: - app: {{ .NAME }}-target-events-1 + app: {{ .NAME }}-target-events-2 spec: selector: - app: {{ .NAME }}-target-events-1 + app: {{ .NAME }}-target-events-2 ports: - port: {{ .PORT }} targetPort: {{ .PORT }} - name: {{ .NAME }}-target-events-1 + name: {{ .NAME }}-target-events-2 protocol: TCP --- apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: blockchain-watcher-target-events-1 + name: blockchain-watcher-target-events-2 namespace: {{ .NAMESPACE }} spec: accessModes: @@ -31,7 +31,7 @@ spec: apiVersion: v1 kind: ConfigMap metadata: - name: {{ .NAME }}-target-events-1-jobs + name: {{ .NAME }}-target-events-2-jobs namespace: {{ .NAMESPACE }} data: testnet-jobs.json: |- @@ -61,7 +61,7 @@ data: "target": "sns", "mapper": "aptosRedeemedTransactionFoundMapper", "config": { - "metricName": "process_source_event" + "metricName": "process_vaa_event" } } ] @@ -94,7 +94,7 @@ data: "target": "sns", "mapper": "aptosRedeemedTransactionFoundMapper", "config": { - "metricName": "process_source_event" + "metricName": "process_vaa_event" } } ] @@ -104,17 +104,17 @@ data: apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .NAME }}-target-events-1 + name: {{ .NAME }}-target-events-2 namespace: {{ .NAMESPACE }} spec: replicas: 1 selector: matchLabels: - app: {{ .NAME }}-target-events-1 + app: {{ .NAME }}-target-events-2 template: metadata: labels: - app: {{ .NAME }}-target-events-1 + app: {{ .NAME }}-target-events-2 annotations: prometheus.io/scrape: "true" prometheus.io/port: "{{ .PORT }}" @@ -205,10 +205,10 @@ spec: volumes: - name: metadata-volume persistentVolumeClaim: - claimName: blockchain-watcher-target-events-1 + claimName: blockchain-watcher-target-events-2 - name: jobs-volume configMap: - name: {{ .NAME }}-target-events-1-jobs + name: {{ .NAME }}-target-events-2-jobs items: - key: {{ .BLOCKCHAIN_ENV }}-jobs.json path: jobs.json From c682888339311c2520f5541a2b1dbb48ce83d1ca Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 11 Mar 2024 10:30:55 -0300 Subject: [PATCH 14/30] Change current cursor value --- blockchain-watcher/src/domain/actions/aptos/PollAptos.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index dc39840c1..a0a51516a 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -10,7 +10,6 @@ export class PollAptos extends RunPollingJob { private readonly getAptos: GetAptosSequences; private lastBlock?: bigint; - private sequenceHeightCursor?: bigint; private previousBlock?: bigint; private getAptosRecords: { [key: string]: any } = { GetAptosSequences, @@ -32,7 +31,6 @@ export class PollAptos extends RunPollingJob { protected async preHook(): Promise { const metadata = await this.metadataRepo.get(this.cfg.id); if (metadata) { - this.sequenceHeightCursor = metadata.lastBlock; this.previousBlock = metadata.previousBlock; this.lastBlock = metadata.lastBlock; } @@ -41,8 +39,8 @@ export class PollAptos extends RunPollingJob { protected async hasNext(): Promise { if ( this.cfg.toSequence && - this.sequenceHeightCursor && - this.sequenceHeightCursor >= BigInt(this.cfg.toSequence) + this.previousBlock && + this.previousBlock >= BigInt(this.cfg.toSequence) ) { this.logger.info( `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.fromBlock} to ${this.cfg.toSequence}` @@ -102,7 +100,7 @@ export class PollAptos extends RunPollingJob { ...labels, type: "max", }); - this.statsRepo.measure("polling_cursor", this.sequenceHeightCursor ?? 0n, { + this.statsRepo.measure("polling_cursor", this.previousBlock ?? 0n, { ...labels, type: "current", }); From eadc1fe4b4a5a30d8fbf1194897fd5a8c4fd522e Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 11 Mar 2024 10:35:46 -0300 Subject: [PATCH 15/30] Improve comments --- .../src/domain/actions/aptos/GetAptosSequences.ts | 12 ++++++------ .../src/domain/actions/aptos/GetAptosTransactions.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index c1a99c0a6..32d4b469d 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -36,14 +36,14 @@ export class GetAptosSequences { opts.filter ); - // update lastBlock with the new lastBlock + // Update lastBlock with the new lastBlock this.lastBlock = BigInt(events[events.length - 1].sequence_number); if (opts.previousBlock == this.lastBlock) { return []; } - // update previousBlock with opts lastBlock + // Update previousBlock with opts lastBlock this.previousBlock = opts.lastBlock; const transactions = await this.repo.getTransactionsByVersionForSourceEvent( @@ -68,7 +68,7 @@ export class GetAptosSequences { savedPreviousSequence: bigint | undefined, savedLastBlock: bigint | undefined ): Block | undefined { - // if [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size + // If [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size if (cfgFromBlock) { return { fromBlock: Number(cfgFromBlock), @@ -77,7 +77,7 @@ export class GetAptosSequences { } if (savedPreviousSequence && savedLastBlock) { - // if process the [same block], return the same lastBlock and toBlock equal the block batch size + // If process the [same block], return the same lastBlock and toBlock equal the block batch size if (savedPreviousSequence === savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -85,7 +85,7 @@ export class GetAptosSequences { }; } - // if process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 + // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 if (savedPreviousSequence !== savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -95,7 +95,7 @@ export class GetAptosSequences { } if (savedLastBlock) { - // if there is [no previous block], return the lastBlock and toBlock equal the block batch size + // If there is [no previous block], return the lastBlock and toBlock equal the block batch size if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { return { fromBlock: Number(savedLastBlock), diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 27f46b8b7..87bb57c3f 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -5,8 +5,8 @@ import { createBatches } from "../../../infrastructure/repositories/common/utils import winston from "winston"; export class GetAptosTransactions { - private readonly repo: AptosRepository; protected readonly logger: winston.Logger; + private readonly repo: AptosRepository; private previousBlock?: bigint; private lastBlock?: bigint; @@ -38,14 +38,14 @@ export class GetAptosTransactions { opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) ); - // update lastBlock with the new lastBlock + // Update lastBlock with the new lastBlock this.lastBlock = BigInt(transaction[transaction.length - 1].version); if (opts.previousBlock == this.lastBlock) { return []; } - // update previousBlock with opts lastBlock + // Update previousBlock with opts lastBlock this.previousBlock = opts.lastBlock; if (transactionsByAddressConfigured.length > 0) { @@ -69,7 +69,7 @@ export class GetAptosTransactions { savedPreviousBlock: bigint | undefined, savedLastBlock: bigint | undefined ): Block | undefined { - // if [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size + // If [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size if (cfgFromBlock) { return { fromBlock: Number(cfgFromBlock), @@ -78,7 +78,7 @@ export class GetAptosTransactions { } if (savedPreviousBlock && savedLastBlock) { - // if process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size + // If process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { return { fromBlock: Number(savedLastBlock), @@ -88,7 +88,7 @@ export class GetAptosTransactions { } if (savedLastBlock) { - // if there is [no previous block], return the lastBlock and toBlock equal the block batch size + // If there is [no previous block], return the lastBlock and toBlock equal the block batch size if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { return { fromBlock: Number(savedLastBlock), From 87e33fd62b533d9bb5e66b52dc0156b9f4ed6427 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Mon, 11 Mar 2024 17:23:19 -0300 Subject: [PATCH 16/30] improve domain event --- .../domain/actions/aptos/GetAptosSequences.ts | 10 ++++---- .../actions/aptos/GetAptosTransactions.ts | 10 ++++---- .../actions/aptos/HandleAptosTransactions.ts | 6 ++--- .../src/domain/actions/aptos/PollAptos.ts | 9 +++++-- .../src/domain/entities/aptos.ts | 18 ++++++++++++++ blockchain-watcher/src/domain/repositories.ts | 7 +++--- .../aptos/aptosLogMessagePublishedMapper.ts | 4 ++-- .../aptosRedeemedTransactionFoundMapper.ts | 4 ++-- .../aptos/AptosJsonRPCBlockRepository.ts | 24 +++---------------- .../RateLimitedAptosJsonRPCBlockRepository.ts | 7 +++--- .../actions/aptos/GetAptosSequences.test.ts | 8 +++---- .../aptos/GetAptosTransactions.test.ts | 8 +++---- .../aptos/HandleAptosTransactions.test.ts | 6 ++--- .../aptosLogMessagePublishedMapper.test.ts | 4 ++-- ...ptosRedeemedTransactionFoundMapper.test.ts | 6 ++--- 15 files changed, 67 insertions(+), 64 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index 32d4b469d..d8cc15d03 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -1,5 +1,5 @@ -import { Block, TransactionFilter } from "./PollAptos"; -import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { Block, Range, TransactionFilter } from "./PollAptos"; +import { AptosTransaction } from "../../entities/aptos"; import { AptosRepository } from "../../repositories"; import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; @@ -16,8 +16,8 @@ export class GetAptosSequences { this.repo = repo; } - async execute(range: Block | undefined, opts: GetAptosOpts): Promise { - let populatedTransactions: TransactionsByVersion[] = []; + async execute(range: Block | undefined, opts: GetAptosOpts): Promise { + let populatedTransactions: AptosTransaction[] = []; this.logger.info( `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - lastBlock: ${opts.lastBlock}]` @@ -105,7 +105,7 @@ export class GetAptosSequences { } } - getUpdatedRange() { + getUpdatedRange(): Range { return { previousBlock: this.previousBlock, lastBlock: this.lastBlock, diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 87bb57c3f..44616954a 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -1,5 +1,5 @@ -import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -import { Block, TransactionFilter } from "./PollAptos"; +import { Block, Range, TransactionFilter } from "./PollAptos"; +import { AptosTransaction } from "../../entities/aptos"; import { AptosRepository } from "../../repositories"; import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; @@ -16,8 +16,8 @@ export class GetAptosTransactions { this.repo = repo; } - async execute(range: Block | undefined, opts: GetAptosOpts): Promise { - let populatedTransactions: TransactionsByVersion[] = []; + async execute(range: Block | undefined, opts: GetAptosOpts): Promise { + let populatedTransactions: AptosTransaction[] = []; this.logger.info( `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` @@ -98,7 +98,7 @@ export class GetAptosTransactions { } } - getUpdatedRange() { + getUpdatedRange(): Range { return { previousBlock: this.previousBlock, lastBlock: this.lastBlock, diff --git a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts index c03876b63..6f5f6dfad 100644 --- a/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/HandleAptosTransactions.ts @@ -1,16 +1,16 @@ import { TransactionFoundEvent } from "../../entities"; -import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosTransaction } from "../../entities/aptos"; import { StatRepository } from "../../repositories"; export class HandleAptosTransactions { constructor( private readonly cfg: HandleAptosTransactionsOptions, - private readonly mapper: (tx: TransactionsByVersion) => TransactionFoundEvent, + private readonly mapper: (tx: AptosTransaction) => TransactionFoundEvent, private readonly target: (parsed: TransactionFoundEvent[]) => Promise, private readonly statsRepo: StatRepository ) {} - public async handle(txs: TransactionsByVersion[]): Promise { + public async handle(txs: AptosTransaction[]): Promise { const items: TransactionFoundEvent[] = []; for (const tx of txs) { diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index a0a51516a..fe7b1b840 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -1,7 +1,7 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; -import { TransactionsByVersion } from "../../../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { GetAptosTransactions } from "./GetAptosTransactions"; import { GetAptosSequences } from "./GetAptosSequences"; +import { AptosTransaction } from "../../entities/aptos"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; @@ -51,7 +51,7 @@ export class PollAptos extends RunPollingJob { return true; } - protected async get(): Promise { + protected async get(): Promise { const range = this.getAptos.getBlockRange( this.cfg.getBlockBatchSize(), this.cfg.fromBlock, @@ -174,3 +174,8 @@ export type Block = { fromBlock?: number; toBlock?: number; }; + +export type Range = { + previousBlock: bigint | undefined; + lastBlock: bigint | undefined; +}; diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index f32a487a3..34906367c 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -11,3 +11,21 @@ export type AptosEvent = Omit & { timestamp: string; }; }; + +export type AptosTransaction = { + consistencyLevel: number; + emitterChain?: number; + blockHeight: bigint; + timestamp: number; + blockTime: number; + sequence: bigint; + version: string; + payload: string; + address: string; + sender: string; + status?: boolean; + events: any; + nonce: number; + hash: string; + type?: string; +}; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index b9def79c6..5a656f11b 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -19,8 +19,7 @@ import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { Block, TransactionFilter } from "./actions/aptos/PollAptos"; -import { TransactionsByVersion } from "../infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; -import { AptosEvent } from "./entities/aptos"; +import { AptosEvent, AptosTransaction } from "./entities/aptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -76,11 +75,11 @@ export interface AptosRepository { getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise; + ): Promise; getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise; + ): Promise; } export interface MetadataRepository { diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index 91eaf46a0..a2c61dad0 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -1,5 +1,5 @@ import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; -import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosTransaction } from "../../../domain/entities/aptos"; import winston from "winston"; const CHAIN_ID_APTOS = 22; @@ -7,7 +7,7 @@ const CHAIN_ID_APTOS = 22; let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); export const aptosLogMessagePublishedMapper = ( - tx: TransactionsByVersion + tx: AptosTransaction ): LogFoundEvent | undefined => { if (!tx.blockTime) { throw new Error(`[aptos] Block time is missing for tx ${tx.hash}`); diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index d8ed10062..e17c9fd82 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -1,5 +1,5 @@ import { TransactionFoundEvent } from "../../../domain/entities"; -import { TransactionsByVersion } from "../../repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosTransaction } from "../../../domain/entities/aptos"; import { CHAIN_ID_APTOS } from "@certusone/wormhole-sdk"; import { findProtocol } from "../contractsMapper"; import winston from "winston"; @@ -9,7 +9,7 @@ let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFo const APTOS_CHAIN = "aptos"; export const aptosRedeemedTransactionFoundMapper = ( - tx: TransactionsByVersion + tx: AptosTransaction ): TransactionFoundEvent | undefined => { const emitterAddress = tx.sender; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 79832ae96..63cccf24d 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,6 +1,6 @@ +import { AptosEvent, AptosTransaction } from "../../../domain/entities/aptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; -import { AptosEvent } from "../../../domain/entities/aptos"; import { parseVaa } from "@certusone/wormhole-sdk"; import winston from "winston"; @@ -36,7 +36,7 @@ export class AptosJsonRPCBlockRepository { async getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise { + ): Promise { try { const transactions = await Promise.all( events.map(async (event) => { @@ -73,7 +73,7 @@ export class AptosJsonRPCBlockRepository { async getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise { + ): Promise { try { const transactions = await Promise.all( events.map(async (event) => { @@ -124,21 +124,3 @@ export class AptosJsonRPCBlockRepository { this.logger.error(`[aptos] Error calling ${method}: ${e.message}`); } } - -export type TransactionsByVersion = { - consistencyLevel: number; - emitterChain?: number; - blockHeight: bigint; - timestamp: number; - blockTime: number; - sequence: bigint; - version: string; - payload: string; - address: string; - sender: string; - status?: boolean; - events: any; - nonce: number; - hash: string; - type?: string; -}; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index 7a02f2e19..7a11e09f4 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -1,8 +1,7 @@ +import { AptosEvent, AptosTransaction } from "../../../domain/entities/aptos"; import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; -import { TransactionsByVersion } from "./AptosJsonRPCBlockRepository"; import { AptosRepository } from "../../../domain/repositories"; -import { AptosEvent } from "../../../domain/entities/aptos"; import { Options } from "../common/rateLimitedOptions"; import winston from "winston"; @@ -22,7 +21,7 @@ export class RateLimitedAptosJsonRPCBlockRepository getTransactionsByVersionForSourceEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise { + ): Promise { return this.breaker .fn(() => this.delegate.getTransactionsByVersionForSourceEvent(events, filter)) .execute(); @@ -31,7 +30,7 @@ export class RateLimitedAptosJsonRPCBlockRepository getTransactionsByVersionForRedeemedEvent( events: AptosEvent[], filter: TransactionFilter - ): Promise { + ): Promise { return this.breaker .fn(() => this.delegate.getTransactionsByVersionForRedeemedEvent(events, filter)) .execute(); diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index 3dab3e734..8a1cc37ac 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, it, expect, jest } from "@jest/globals"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { thenWaitForAssertion } from "../../../wait-assertion"; +import { AptosTransaction } from "../../../../src/domain/entities/aptos"; import { PollAptosTransactionsMetadata, PollAptosTransactionsConfig, @@ -18,15 +18,15 @@ let getTransactionsByVersionsForSourceEventSpy: jest.SpiedFunction< let getSequenceNumberSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; -let handlerSpy: jest.SpiedFunction<(txs: TransactionsByVersion[]) => Promise>; +let handlerSpy: jest.SpiedFunction<(txs: AptosTransaction[]) => Promise>; let metadataRepo: MetadataRepository; let aptosRepo: AptosRepository; let statsRepo: StatRepository; let handlers = { - working: (txs: TransactionsByVersion[]) => Promise.resolve(), - failing: (txs: TransactionsByVersion[]) => Promise.reject(), + working: (txs: AptosTransaction[]) => Promise.resolve(), + failing: (txs: AptosTransaction[]) => Promise.reject(), }; let pollAptos: PollAptos; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index 23a3c5f68..342b8ea2f 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, it, expect, jest } from "@jest/globals"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { thenWaitForAssertion } from "../../../wait-assertion"; +import { AptosTransaction } from "../../../../src/domain/entities/aptos"; import { PollAptosTransactionsMetadata, PollAptosTransactionsConfig, @@ -18,15 +18,15 @@ let getTransactionsByVersionsForRedeemedEventSpy: jest.SpiedFunction< let getTransactionsSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; -let handlerSpy: jest.SpiedFunction<(txs: TransactionsByVersion[]) => Promise>; +let handlerSpy: jest.SpiedFunction<(txs: AptosTransaction[]) => Promise>; let metadataRepo: MetadataRepository; let aptosRepo: AptosRepository; let statsRepo: StatRepository; let handlers = { - working: (txs: TransactionsByVersion[]) => Promise.resolve(), - failing: (txs: TransactionsByVersion[]) => Promise.reject(), + working: (txs: AptosTransaction[]) => Promise.resolve(), + failing: (txs: AptosTransaction[]) => Promise.reject(), }; let pollAptos: PollAptos; diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index 052e8dc62..546558a62 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, it, expect, jest } from "@jest/globals"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; +import { AptosTransaction } from "../../../../src/domain/entities/aptos"; import { StatRepository } from "../../../../src/domain/repositories"; import { LogFoundEvent } from "../../../../src/domain/entities"; import { @@ -11,7 +11,7 @@ let targetRepoSpy: jest.SpiedFunction<(typeof targetRepo)["save"]>; let statsRepo: StatRepository; let handleAptosTransactions: HandleAptosTransactions; -let txs: TransactionsByVersion[]; +let txs: AptosTransaction[]; let cfg: HandleAptosTransactionsOptions; describe("HandleAptosTransactions", () => { @@ -39,7 +39,7 @@ describe("HandleAptosTransactions", () => { }); }); -const mapper = (tx: TransactionsByVersion) => { +const mapper = (tx: AptosTransaction) => { return { name: "log-message-published", address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index 5c77587be..e298d323b 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -1,6 +1,6 @@ import { aptosLogMessagePublishedMapper } from "../../../../src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { describe, it, expect } from "@jest/globals"; +import { AptosTransaction } from "../../../../src/domain/entities/aptos"; describe("aptosLogMessagePublishedMapper", () => { it("should be able to map log to aptosLogMessagePublishedMapper", async () => { @@ -30,7 +30,7 @@ describe("aptosLogMessagePublishedMapper", () => { }); }); -const txs: TransactionsByVersion = { +const txs: AptosTransaction = { consistencyLevel: 0, blockHeight: 153517771n, timestamp: 170963869344, diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts index ae5c0046d..56465c715 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts @@ -1,6 +1,6 @@ import { aptosRedeemedTransactionFoundMapper } from "../../../../src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper"; -import { TransactionsByVersion } from "../../../../src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository"; import { describe, it, expect } from "@jest/globals"; +import { AptosTransaction } from "../../../../src/domain/entities/aptos"; describe("aptosRedeemedTransactionFoundMapper", () => { it("should be able to map log to aptosRedeemedTransactionFoundMapper", async () => { @@ -32,7 +32,7 @@ describe("aptosRedeemedTransactionFoundMapper", () => { it("should not be able to map log to aptosRedeemedTransactionFoundMapper", async () => { // Given - const tx: TransactionsByVersion = { + const tx: AptosTransaction = { consistencyLevel: 15, emitterChain: 5, blockHeight: 154363203n, @@ -58,7 +58,7 @@ describe("aptosRedeemedTransactionFoundMapper", () => { }); }); -const tx: TransactionsByVersion = { +const tx: AptosTransaction = { consistencyLevel: 15, emitterChain: 5, blockHeight: 154363203n, From f8840466a3dd570039a0aa2df556e200a89d9c00 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Tue, 12 Mar 2024 14:26:24 -0300 Subject: [PATCH 17/30] Add validation about differents blocks number --- .../domain/actions/aptos/GetAptosSequences.ts | 8 +++---- .../actions/aptos/GetAptosTransactions.ts | 8 ++++++- .../aptos/GetAptosTransactions.test.ts | 21 ++++++++++++++++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index d8cc15d03..213abdfbe 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -83,13 +83,11 @@ export class GetAptosSequences { fromBlock: Number(savedLastBlock), toBlock: cfgBlockBarchSize, }; - } - - // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 - if (savedPreviousSequence !== savedLastBlock) { + } else { + // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 return { fromBlock: Number(savedLastBlock), - toBlock: Number(savedLastBlock) - Number(savedPreviousSequence) + 1, + toBlock: Number(savedLastBlock - savedPreviousSequence) + 1, }; } } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 44616954a..a97e91981 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -79,11 +79,17 @@ export class GetAptosTransactions { if (savedPreviousBlock && savedLastBlock) { // If process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size - if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { + if (savedPreviousBlock === savedLastBlock) { return { fromBlock: Number(savedLastBlock), toBlock: cfgBlockBarchSize, }; + } else { + // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 + return { + fromBlock: Number(savedLastBlock), + toBlock: Number(savedLastBlock - savedPreviousBlock) + 1, + }; } } diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index 342b8ea2f..9fec008e4 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -289,7 +289,7 @@ describe("GetAptosTransactions", () => { ); }); - it("should be return the same lastBlock and toBlock equal 100", async () => { + it("should be return the same lastBlock and toBlock equal 1000", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); @@ -308,6 +308,25 @@ describe("GetAptosTransactions", () => { ); }); + it("should be return the lastBlock and toBlock with the difference between last and to block plus 1", async () => { + // Given + givenAptosBlockRepository(); + givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146540n }); + givenStatsRepository(); + // Se fromBlock for cfg + props.fromBlock = 0n; + givenPollAptosTx(cfg); + + // Whem + await whenPollEvmLogsStarts(); + + // Then + await thenWaitForAssertion( + () => expect(getTransactionsSpy).toHaveReturnedTimes(1), + () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146540, toBlock: 100 }) + ); + }); + it("should be if return the lastBlock and toBlock equal the block batch size", async () => { // Given givenAptosBlockRepository(); From 661215413580edc0393da2b7bc47b9910ba7dc67 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Tue, 12 Mar 2024 15:20:10 -0300 Subject: [PATCH 18/30] Improve transaction and sequence implementation --- .../src/domain/actions/aptos/GetAptosSequences.ts | 2 +- .../src/domain/actions/aptos/GetAptosTransactions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index 213abdfbe..7cc7f17ae 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -87,7 +87,7 @@ export class GetAptosSequences { // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 return { fromBlock: Number(savedLastBlock), - toBlock: Number(savedLastBlock - savedPreviousSequence) + 1, + toBlock: Number(savedLastBlock) - Number(savedPreviousSequence) + 1, }; } } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index a97e91981..b330b8243 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -88,7 +88,7 @@ export class GetAptosTransactions { // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 return { fromBlock: Number(savedLastBlock), - toBlock: Number(savedLastBlock - savedPreviousBlock) + 1, + toBlock: Number(savedLastBlock) - Number(savedPreviousBlock) + 1, }; } } From 8a04db7c65b1572c73874a6c7d353712e08c41a3 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Tue, 12 Mar 2024 17:24:59 -0300 Subject: [PATCH 19/30] Set batches --- .../actions/aptos/GetAptosTransactions.ts | 8 +------- .../aptos/GetAptosTransactions.test.ts | 19 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index b330b8243..44616954a 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -79,17 +79,11 @@ export class GetAptosTransactions { if (savedPreviousBlock && savedLastBlock) { // If process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size - if (savedPreviousBlock === savedLastBlock) { + if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { return { fromBlock: Number(savedLastBlock), toBlock: cfgBlockBarchSize, }; - } else { - // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 - return { - fromBlock: Number(savedLastBlock), - toBlock: Number(savedLastBlock) - Number(savedPreviousBlock) + 1, - }; } } diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index 9fec008e4..e7784a168 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -308,25 +308,6 @@ describe("GetAptosTransactions", () => { ); }); - it("should be return the lastBlock and toBlock with the difference between last and to block plus 1", async () => { - // Given - givenAptosBlockRepository(); - givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146540n }); - givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 0n; - givenPollAptosTx(cfg); - - // Whem - await whenPollEvmLogsStarts(); - - // Then - await thenWaitForAssertion( - () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146540, toBlock: 100 }) - ); - }); - it("should be if return the lastBlock and toBlock equal the block batch size", async () => { // Given givenAptosBlockRepository(); From bc0b87cf169590bf5c411cd02a09f42a2e32cc08 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 13:59:55 -0300 Subject: [PATCH 20/30] Resolve comment in PR --- .../domain/actions/aptos/GetAptosSequences.ts | 99 ++++++++--------- .../actions/aptos/GetAptosTransactions.ts | 90 ++++++++-------- .../src/domain/actions/aptos/PollAptos.ts | 77 ++++++------- .../src/domain/entities/aptos.ts | 38 +++++-- blockchain-watcher/src/domain/repositories.ts | 15 +-- .../aptos/aptosLogMessagePublishedMapper.ts | 27 +++-- .../aptosRedeemedTransactionFoundMapper.ts | 19 ++-- .../aptos/AptosJsonRPCBlockRepository.ts | 82 ++++---------- .../RateLimitedAptosJsonRPCBlockRepository.ts | 26 ++--- .../repositories/common/utils.ts | 21 +--- .../rpc/http/InstrumentedAptosProvider.ts | 101 ++++++++++++++---- .../actions/aptos/GetAptosSequences.test.ts | 99 +++++++++++------ .../aptos/GetAptosTransactions.test.ts | 52 ++++----- .../aptos/HandleAptosTransactions.test.ts | 3 +- .../aptosLogMessagePublishedMapper.test.ts | 10 +- ...ptosRedeemedTransactionFoundMapper.test.ts | 42 ++++++-- 16 files changed, 421 insertions(+), 380 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index 7cc7f17ae..d929ebdec 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -1,119 +1,114 @@ -import { Block, Range, TransactionFilter } from "./PollAptos"; +import { GetAptosOpts, PreviousRange, Range } from "./PollAptos"; import { AptosTransaction } from "../../entities/aptos"; import { AptosRepository } from "../../repositories"; -import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; export class GetAptosSequences { protected readonly logger: winston.Logger; private readonly repo: AptosRepository; - private previousBlock?: bigint; - private lastBlock?: bigint; + private previousFrom?: bigint; + private lastFrom?: bigint; constructor(repo: AptosRepository) { this.logger = winston.child({ module: "GetAptosSequences" }); this.repo = repo; } - async execute(range: Block | undefined, opts: GetAptosOpts): Promise { + async execute(range: Range | undefined, opts: GetAptosOpts): Promise { let populatedTransactions: AptosTransaction[] = []; this.logger.info( - `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - lastBlock: ${opts.lastBlock}]` + `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const batches = createBatches(range); + const incrementBatchIndex = 100; + const limitBatch = opts.previousFrom + ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 + : 100; + let limit = limitBatch < 100 ? 1 : 100; - for (const toBatch of batches) { - const fromBatch = this.lastBlock ? Number(this.lastBlock) : range?.fromBlock; + while (limit <= limitBatch) { + const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; - const events = await this.repo.getSequenceNumber( + const events = await this.repo.getEventsByEventHandle( { - fromBlock: fromBatch, - toBlock: toBatch, + from: fromBatch, + limit: limitBatch, }, opts.filter ); - // Update lastBlock with the new lastBlock - this.lastBlock = BigInt(events[events.length - 1].sequence_number); + // Update lastFrom with the new lastFrom + this.lastFrom = BigInt(events[events.length - 1].sequence_number); - if (opts.previousBlock == this.lastBlock) { + if (opts.previousFrom == this.lastFrom) { return []; } - // Update previousBlock with opts lastBlock - this.previousBlock = opts.lastBlock; + // Update previousFrom with opts lastFrom + this.previousFrom = opts.lastFrom; - const transactions = await this.repo.getTransactionsByVersionForSourceEvent( - events, - opts.filter - ); + const transactions = await this.repo.getTransactionsByVersion(events, opts.filter); transactions.forEach((tx) => { populatedTransactions.push(tx); }); + + limit += incrementBatchIndex; } this.logger.info( - `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][block: ${range?.fromBlock}]` + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][from: ${range?.from}]` ); return populatedTransactions; } getBlockRange( cfgBlockBarchSize: number, - cfgFromBlock: bigint | undefined, + cfgFrom: bigint | undefined, savedPreviousSequence: bigint | undefined, - savedLastBlock: bigint | undefined - ): Block | undefined { - // If [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size - if (cfgFromBlock) { + savedlastFrom: bigint | undefined + ): Range | undefined { + // If [set up a from for cfg], return the from and limit equal the from batch size + if (cfgFrom) { return { - fromBlock: Number(cfgFromBlock), - toBlock: cfgBlockBarchSize, + from: Number(cfgFrom), + limit: cfgBlockBarchSize, }; } - if (savedPreviousSequence && savedLastBlock) { - // If process the [same block], return the same lastBlock and toBlock equal the block batch size - if (savedPreviousSequence === savedLastBlock) { + if (savedPreviousSequence && savedlastFrom) { + // If process the [same from], return the same lastFrom and limit equal the from batch size + if (savedPreviousSequence === savedlastFrom) { return { - fromBlock: Number(savedLastBlock), - toBlock: cfgBlockBarchSize, + from: Number(savedlastFrom), + limit: cfgBlockBarchSize, }; } else { - // If process [different sequences], return the difference between the lastBlock and the previousBlock plus 1 + // If process [different sequences], return the difference between the lastFrom and the previousFrom plus 1 return { - fromBlock: Number(savedLastBlock), - toBlock: Number(savedLastBlock) - Number(savedPreviousSequence) + 1, + from: Number(savedlastFrom), + limit: Number(savedlastFrom) - Number(savedPreviousSequence) + 1, }; } } - if (savedLastBlock) { - // If there is [no previous block], return the lastBlock and toBlock equal the block batch size - if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { + if (savedlastFrom) { + // If there is [no previous from], return the lastFrom and limit equal the from batch size + if (!cfgFrom || BigInt(cfgFrom) < savedlastFrom) { return { - fromBlock: Number(savedLastBlock), - toBlock: cfgBlockBarchSize, + from: Number(savedlastFrom), + limit: cfgBlockBarchSize, }; } } } - getUpdatedRange(): Range { + getUpdatedRange(): PreviousRange { return { - previousBlock: this.previousBlock, - lastBlock: this.lastBlock, + previousFrom: this.previousFrom, + lastFrom: this.lastFrom, }; } } - -export type GetAptosOpts = { - addresses: string[]; - filter: TransactionFilter; - previousBlock?: bigint | undefined; - lastBlock?: bigint | undefined; -}; diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 44616954a..4dbaa746b 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -1,55 +1,56 @@ -import { Block, Range, TransactionFilter } from "./PollAptos"; +import { Range, PreviousRange, GetAptosOpts } from "./PollAptos"; import { AptosTransaction } from "../../entities/aptos"; import { AptosRepository } from "../../repositories"; -import { createBatches } from "../../../infrastructure/repositories/common/utils"; import winston from "winston"; export class GetAptosTransactions { protected readonly logger: winston.Logger; private readonly repo: AptosRepository; - private previousBlock?: bigint; - private lastBlock?: bigint; + private previousFrom?: bigint; + private lastFrom?: bigint; constructor(repo: AptosRepository) { this.logger = winston.child({ module: "GetAptosTransactions" }); this.repo = repo; } - async execute(range: Block | undefined, opts: GetAptosOpts): Promise { + async execute(range: Range | undefined, opts: GetAptosOpts): Promise { let populatedTransactions: AptosTransaction[] = []; this.logger.info( - `[aptos][exec] Processing blocks [previousBlock: ${opts.previousBlock} - latestBlock: ${opts.lastBlock}]` + `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const batches = createBatches(range); + const incrementBatchIndex = 100; + const limitBatch = range?.limit ?? 100; + let limit = 100; - for (const toBatch of batches) { - const fromBatch = this.lastBlock ? Number(this.lastBlock) : range?.fromBlock; + while (limit <= limitBatch) { + const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; - const transaction = await this.repo.getTransactions({ - fromBlock: fromBatch, - toBlock: toBatch, + const transactions = await this.repo.getTransactions({ + from: fromBatch, + limit: limit, }); // Only process transactions to the contract address configured - const transactionsByAddressConfigured = transaction.filter((transaction) => + const transactionsByAddressConfigured = transactions.filter((transaction) => opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) ); - // Update lastBlock with the new lastBlock - this.lastBlock = BigInt(transaction[transaction.length - 1].version); + // Update lastFrom with the new lastFrom + this.lastFrom = BigInt(transactions[transactions.length - 1].version!); - if (opts.previousBlock == this.lastBlock) { + if (opts.previousFrom == this.lastFrom) { return []; } - // Update previousBlock with opts lastBlock - this.previousBlock = opts.lastBlock; + // Update previousFrom with opts lastFrom + this.previousFrom = opts.lastFrom; if (transactionsByAddressConfigured.length > 0) { - const transactions = await this.repo.getTransactionsByVersionForRedeemedEvent( + const transactions = await this.repo.getTransactionsByVersion( transactionsByAddressConfigured, opts.filter ); @@ -58,6 +59,8 @@ export class GetAptosTransactions { populatedTransactions.push(tx); }); } + + limit += incrementBatchIndex; } return populatedTransactions; @@ -65,50 +68,43 @@ export class GetAptosTransactions { getBlockRange( cfgBlockBarchSize: number, - cfgFromBlock: bigint | undefined, - savedPreviousBlock: bigint | undefined, - savedLastBlock: bigint | undefined - ): Block | undefined { - // If [set up a from block for cfg], return the fromBlock and toBlock equal the block batch size - if (cfgFromBlock) { + cfgFrom: bigint | undefined, + savedpreviousFrom: bigint | undefined, + savedlastFrom: bigint | undefined + ): Range | undefined { + // If [set up a from for cfg], return the from and limit equal the from batch size + if (cfgFrom) { return { - fromBlock: Number(cfgFromBlock), - toBlock: cfgBlockBarchSize, + from: Number(cfgFrom), + limit: cfgBlockBarchSize, }; } - if (savedPreviousBlock && savedLastBlock) { - // If process [equal or different blocks], return the same lastBlock and toBlock equal the block batch size - if (savedPreviousBlock === savedLastBlock || savedPreviousBlock !== savedLastBlock) { + if (savedpreviousFrom && savedlastFrom) { + // If process [equal or different blocks], return the same lastFrom and limit equal the from batch size + if (savedpreviousFrom === savedlastFrom || savedpreviousFrom !== savedlastFrom) { return { - fromBlock: Number(savedLastBlock), - toBlock: cfgBlockBarchSize, + from: Number(savedlastFrom), + limit: cfgBlockBarchSize, }; } } - if (savedLastBlock) { - // If there is [no previous block], return the lastBlock and toBlock equal the block batch size - if (!cfgFromBlock || BigInt(cfgFromBlock) < savedLastBlock) { + if (savedlastFrom) { + // If there is [no previous from], return the lastFrom and limit equal the from batch size + if (!cfgFrom || BigInt(cfgFrom) < savedlastFrom) { return { - fromBlock: Number(savedLastBlock), - toBlock: cfgBlockBarchSize, + from: Number(savedlastFrom), + limit: cfgBlockBarchSize, }; } } } - getUpdatedRange(): Range { + getUpdatedRange(): PreviousRange { return { - previousBlock: this.previousBlock, - lastBlock: this.lastBlock, + previousFrom: this.previousFrom, + lastFrom: this.lastFrom, }; } } - -export type GetAptosOpts = { - addresses: string[]; - filter: TransactionFilter; - previousBlock?: bigint | undefined; - lastBlock?: bigint | undefined; -}; diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index fe7b1b840..a20d9b20f 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -9,8 +9,8 @@ export class PollAptos extends RunPollingJob { protected readonly logger: Logger; private readonly getAptos: GetAptosSequences; - private lastBlock?: bigint; - private previousBlock?: bigint; + private previousFrom?: bigint; + private lastFrom?: bigint; private getAptosRecords: { [key: string]: any } = { GetAptosSequences, GetAptosTransactions, @@ -31,19 +31,15 @@ export class PollAptos extends RunPollingJob { protected async preHook(): Promise { const metadata = await this.metadataRepo.get(this.cfg.id); if (metadata) { - this.previousBlock = metadata.previousBlock; - this.lastBlock = metadata.lastBlock; + this.previousFrom = metadata.previousFrom; + this.lastFrom = metadata.lastFrom; } } protected async hasNext(): Promise { - if ( - this.cfg.toSequence && - this.previousBlock && - this.previousBlock >= BigInt(this.cfg.toSequence) - ) { + if (this.cfg.limit && this.previousFrom && this.previousFrom >= BigInt(this.cfg.limit)) { this.logger.info( - `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.fromBlock} to ${this.cfg.toSequence}` + `[aptos][PollAptos] Finished processing all transactions from sequence ${this.cfg.from} to ${this.cfg.limit}` ); return false; } @@ -54,16 +50,16 @@ export class PollAptos extends RunPollingJob { protected async get(): Promise { const range = this.getAptos.getBlockRange( this.cfg.getBlockBatchSize(), - this.cfg.fromBlock, - this.previousBlock, - this.lastBlock + this.cfg.from, + this.previousFrom, + this.lastFrom ); const records = await this.getAptos.execute(range, { addresses: this.cfg.addresses, filter: this.cfg.filter, - previousBlock: this.previousBlock, - lastBlock: this.lastBlock, + previousFrom: this.previousFrom, + lastFrom: this.lastFrom, }); this.updateBlockRange(); @@ -72,19 +68,19 @@ export class PollAptos extends RunPollingJob { } private updateBlockRange(): void { - // Update the previousBlock and lastBlock based on the executed range + // Update the previousFrom and lastFrom based on the executed range const updatedRange = this.getAptos.getUpdatedRange(); if (updatedRange) { - this.previousBlock = updatedRange.previousBlock; - this.lastBlock = updatedRange.lastBlock; + this.previousFrom = updatedRange.previousFrom; + this.lastFrom = updatedRange.lastFrom; } } protected async persist(): Promise { - if (this.lastBlock) { + if (this.lastFrom) { await this.metadataRepo.save(this.cfg.id, { - previousBlock: this.previousBlock, - lastBlock: this.lastBlock, + previousFrom: this.previousFrom, + lastFrom: this.lastFrom, }); } } @@ -96,11 +92,11 @@ export class PollAptos extends RunPollingJob { commitment: this.cfg.getCommitment(), }; this.statsRepo.count("job_execution", labels); - this.statsRepo.measure("polling_cursor", this.lastBlock ?? 0n, { + this.statsRepo.measure("polling_cursor", this.lastFrom ?? 0n, { ...labels, type: "max", }); - this.statsRepo.measure("polling_cursor", this.previousBlock ?? 0n, { + this.statsRepo.measure("polling_cursor", this.previousFrom ?? 0n, { ...labels, type: "current", }); @@ -126,12 +122,12 @@ export class PollAptosTransactionsConfig { return this.props.interval; } - public get fromBlock(): bigint | undefined { - return this.props.fromBlock ? BigInt(this.props.fromBlock) : undefined; + public get from(): bigint | undefined { + return this.props.from ? BigInt(this.props.from) : undefined; } - public get toSequence(): bigint | undefined { - return this.props.toSequence ? BigInt(this.props.toSequence) : undefined; + public get limit(): bigint | undefined { + return this.props.limit ? BigInt(this.props.limit) : undefined; } public get filter(): TransactionFilter { @@ -145,8 +141,8 @@ export class PollAptosTransactionsConfig { export interface PollAptosTransactionsConfigProps { blockBatchSize?: number; - fromBlock?: bigint; - toSequence?: bigint; + from?: bigint; + limit?: bigint; environment: string; commitment?: string; addresses: string[]; @@ -159,8 +155,8 @@ export interface PollAptosTransactionsConfigProps { } export type PollAptosTransactionsMetadata = { - previousBlock?: bigint; - lastBlock?: bigint; + previousFrom?: bigint; + lastFrom?: bigint; }; export type TransactionFilter = { @@ -170,12 +166,19 @@ export type TransactionFilter = { type?: string; }; -export type Block = { - fromBlock?: number; - toBlock?: number; +export type Range = { + from?: number; + limit?: number; }; -export type Range = { - previousBlock: bigint | undefined; - lastBlock: bigint | undefined; +export type PreviousRange = { + previousFrom: bigint | undefined; + lastFrom: bigint | undefined; +}; + +export type GetAptosOpts = { + addresses: string[]; + filter: TransactionFilter; + previousFrom?: bigint | undefined; + lastFrom?: bigint | undefined; }; diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 34906367c..2e031aad2 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -1,11 +1,16 @@ import { Types } from "aptos"; export type AptosEvent = Omit & { - version: string; + hash?: string; + events?: any; + success?: boolean; + timestamp?: string; + version?: string; + payload?: any; data: { consistency_level: number; nonce: string; - payload: string; + payload: any; sender: string; sequence: string; timestamp: string; @@ -13,19 +18,32 @@ export type AptosEvent = Omit & { }; export type AptosTransaction = { - consistencyLevel: number; - emitterChain?: number; + consistencyLevel?: number; blockHeight: bigint; - timestamp: number; - blockTime: number; - sequence: bigint; + timestamp?: number; + blockTime?: number; version: string; - payload: string; + payload?: any; address: string; - sender: string; status?: boolean; events: any; - nonce: number; + nonce?: number; hash: string; type?: string; + to: string; +}; + +export type AptosTransactionByVersion = { + sequence_number?: string; + timestamp?: string; + success?: boolean; + version?: string; + payload?: any; + events?: any[]; + sender?: string; + hash: string; +}; + +export type AptosBlockByVersion = { + block_height: string; }; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 5a656f11b..b08d9c740 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -18,7 +18,7 @@ import { import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; -import { Block, TransactionFilter } from "./actions/aptos/PollAptos"; +import { TransactionFilter } from "./actions/aptos/PollAptos"; import { AptosEvent, AptosTransaction } from "./entities/aptos"; export interface EvmBlockRepository { @@ -70,13 +70,14 @@ export interface SuiRepository { } export interface AptosRepository { - getTransactions(range: Block | undefined): Promise; - getSequenceNumber(range: Block | undefined, filter: TransactionFilter): Promise; - getTransactionsByVersionForSourceEvent( - events: AptosEvent[], + getTransactions( + range: { from?: number | undefined; limit?: number | undefined } | undefined + ): Promise; + getEventsByEventHandle( + range: { from?: number | undefined; limit?: number | undefined } | undefined, filter: TransactionFilter - ): Promise; - getTransactionsByVersionForRedeemedEvent( + ): Promise; + getTransactionsByVersion( events: AptosEvent[], filter: TransactionFilter ): Promise; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index a2c61dad0..e0a620805 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -7,29 +7,28 @@ const CHAIN_ID_APTOS = 22; let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); export const aptosLogMessagePublishedMapper = ( - tx: AptosTransaction + transaction: AptosTransaction ): LogFoundEvent | undefined => { - if (!tx.blockTime) { - throw new Error(`[aptos] Block time is missing for tx ${tx.hash}`); - } + const wormholeEvent = transaction.events.find((tx: any) => tx.type === transaction.type); + const wormholeData = wormholeEvent.data; logger.info( - `[aptos] Source event info: [tx: ${tx.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${tx.sender}}][sequence: ${tx.sequence}]` + `[aptos] Source event info: [tx: ${transaction.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${wormholeData.sender}}][sequence: ${wormholeData.sequence}]` ); return { name: "log-message-published", - address: tx.address, + address: transaction.address, chainId: CHAIN_ID_APTOS, - txHash: tx.hash, - blockHeight: tx.blockHeight, - blockTime: tx.timestamp, + txHash: transaction.hash, + blockHeight: transaction.blockHeight, + blockTime: wormholeData.timestamp, attributes: { - sender: tx.sender, - sequence: Number(tx.sequence), - payload: tx.payload, - nonce: Number(tx.nonce), - consistencyLevel: tx.consistencyLevel, + sender: wormholeData.sender, + sequence: Number(wormholeData.sequence), + payload: wormholeData.payload, + nonce: Number(wormholeData.nonce), + consistencyLevel: transaction.consistencyLevel!, }, }; }; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index e17c9fd82..b166c9a6c 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -2,6 +2,7 @@ import { TransactionFoundEvent } from "../../../domain/entities"; import { AptosTransaction } from "../../../domain/entities/aptos"; import { CHAIN_ID_APTOS } from "@certusone/wormhole-sdk"; import { findProtocol } from "../contractsMapper"; +import { parseVaa } from "@certusone/wormhole-sdk"; import winston from "winston"; let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFoundMapper" }); @@ -11,27 +12,29 @@ const APTOS_CHAIN = "aptos"; export const aptosRedeemedTransactionFoundMapper = ( tx: AptosTransaction ): TransactionFoundEvent | undefined => { - const emitterAddress = tx.sender; + const protocol = findProtocol(APTOS_CHAIN, tx.to, tx.type!, tx.hash); - const protocol = findProtocol(APTOS_CHAIN, tx.address, tx.type!, tx.hash); + const vaaBuffer = Buffer.from(tx.payload?.arguments[0]?.substring(2), "hex"); + const vaa = parseVaa(vaaBuffer); + const emitterAddress = vaa.emitterAddress.toString("hex"); if (protocol && protocol.type && protocol.method) { logger.info( - `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${tx.emitterChain}/${emitterAddress}/${tx.sequence}]` + `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${vaa.emitterChain}/${emitterAddress}/${vaa.sequence}]` ); return { name: "transfer-redeemed", - address: tx.address, + address: tx.to, blockHeight: tx.blockHeight, - blockTime: tx.blockTime, + blockTime: vaa.timestamp, chainId: CHAIN_ID_APTOS, txHash: tx.hash, attributes: { - from: tx.sender, - emitterChain: tx.emitterChain, + from: tx.address, + emitterChain: vaa.emitterChain, emitterAddress: emitterAddress, - sequence: Number(tx.sequence), + sequence: Number(vaa.sequence), status: tx.status === true ? TxStatus.Completed : TxStatus.Failed, protocol: protocol.method, }, diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 63cccf24d..0e6e67b46 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,23 +1,23 @@ import { AptosEvent, AptosTransaction } from "../../../domain/entities/aptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; -import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; -import { parseVaa } from "@certusone/wormhole-sdk"; +import { Range, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; +import { AptosRepository } from "../../../domain/repositories"; import winston from "winston"; -export class AptosJsonRPCBlockRepository { +export class AptosJsonRPCBlockRepository implements AptosRepository { private readonly logger: winston.Logger; constructor(private readonly client: InstrumentedAptosProvider) { this.logger = winston.child({ module: "AptosJsonRPCBlockRepository" }); } - async getSequenceNumber( - range: Block | undefined, + async getEventsByEventHandle( + range: Range | undefined, filter: TransactionFilter ): Promise { try { - const fromBlock = range?.fromBlock ? Number(range?.fromBlock) : undefined; - const toSequence = range?.toBlock ? Number(range?.toBlock) : undefined; + const fromBlock = range?.from ? Number(range?.from) : undefined; + const toSequence = range?.limit ? Number(range?.limit) : undefined; const results = await this.client.getEventsByEventHandle( filter.address, @@ -26,51 +26,15 @@ export class AptosJsonRPCBlockRepository { fromBlock, toSequence ); - return results; - } catch (e) { - this.handleError(e, "getSequenceNumber"); - throw e; - } - } - - async getTransactionsByVersionForSourceEvent( - events: AptosEvent[], - filter: TransactionFilter - ): Promise { - try { - const transactions = await Promise.all( - events.map(async (event) => { - const transaction = await this.client.getTransactionByVersion(Number(event.version)); - const block = await this.client.getBlockByVersion(Number(event.version)); - const wormholeEvent = transaction.events.find((tx: any) => tx.type === filter.type); - - return { - consistencyLevel: event.data.consistency_level, - blockHeight: block.block_height, - timestamp: wormholeEvent.data.timestamp, - blockTime: wormholeEvent.data.timestamp, - sequence: wormholeEvent.data.sequence, - version: transaction.version, - payload: wormholeEvent.data.payload, - address: filter.address, - sender: wormholeEvent.data.sender, - status: transaction.success, - events: transaction.events, - nonce: wormholeEvent.data.nonce, - hash: transaction.hash, - }; - }) - ); - - return transactions; + return results; } catch (e) { - this.handleError(e, "getTransactionsByVersionForSourceEvent"); + this.handleError(e, "getEventsByEventHandle"); throw e; } } - async getTransactionsByVersionForRedeemedEvent( + async getTransactionsByVersion( events: AptosEvent[], filter: TransactionFilter ): Promise { @@ -80,39 +44,31 @@ export class AptosJsonRPCBlockRepository { const transaction = await this.client.getTransactionByVersion(Number(event.version)); const block = await this.client.getBlockByVersion(Number(event.version)); - const vaaBuffer = Buffer.from(transaction.payload.arguments[0].substring(2), "hex"); - const vaa = parseVaa(vaaBuffer); - return { - consistencyLevel: vaa.consistencyLevel, - emitterChain: vaa.emitterChain, - blockHeight: block.block_height, - timestamp: vaa.timestamp, - blockTime: vaa.timestamp, - sequence: vaa.sequence, - version: transaction.version, - payload: vaa.payload.toString("hex"), - address: filter.address, - sender: vaa.emitterAddress.toString("hex"), + consistencyLevel: event?.data?.consistency_level, + blockHeight: BigInt(block.block_height), + version: transaction.version!, + address: event.events ? event.events[0].guid.account_address : filter.address, status: transaction.success, events: transaction.events, - nonce: vaa.nonce, hash: transaction.hash, type: filter.type, + payload: transaction.payload, + to: filter.address, }; }) ); return transactions; } catch (e) { - this.handleError(e, "getTransactionsByVersionForRedeemedEvent"); + this.handleError(e, "getTransactionsByVersionForSourceEvent"); throw e; } } - async getTransactions(block: Block): Promise { + async getTransactions(range: Range): Promise { try { - const results = await this.client.getTransactions(block); + const results = await this.client.getTransactions(range); return results; } catch (e) { this.handleError(e, "getTransactions"); diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index 7a11e09f4..b3849b47e 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -1,5 +1,5 @@ import { AptosEvent, AptosTransaction } from "../../../domain/entities/aptos"; -import { Block, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; +import { Range, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository"; import { AptosRepository } from "../../../domain/repositories"; import { Options } from "../common/rateLimitedOptions"; @@ -14,29 +14,21 @@ export class RateLimitedAptosJsonRPCBlockRepository this.logger = winston.child({ module: "RateLimitedAptosJsonRPCBlockRepository" }); } - getSequenceNumber(range: Block | undefined, filter: TransactionFilter): Promise { - return this.breaker.fn(() => this.delegate.getSequenceNumber(range, filter)).execute(); + getTransactions(range: Range | undefined): Promise { + return this.breaker.fn(() => this.delegate.getTransactions(range)).execute(); } - getTransactionsByVersionForSourceEvent( - events: AptosEvent[], + getEventsByEventHandle( + range: Range | undefined, filter: TransactionFilter - ): Promise { - return this.breaker - .fn(() => this.delegate.getTransactionsByVersionForSourceEvent(events, filter)) - .execute(); + ): Promise { + return this.breaker.fn(() => this.delegate.getEventsByEventHandle(range, filter)).execute(); } - getTransactionsByVersionForRedeemedEvent( + getTransactionsByVersion( events: AptosEvent[], filter: TransactionFilter ): Promise { - return this.breaker - .fn(() => this.delegate.getTransactionsByVersionForRedeemedEvent(events, filter)) - .execute(); - } - - getTransactions(range: Block | undefined): Promise { - return this.breaker.fn(() => this.delegate.getTransactions(range)).execute(); + return this.breaker.fn(() => this.delegate.getTransactionsByVersion(events, filter)).execute(); } } diff --git a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts index fb70ac2a5..6e70b006d 100644 --- a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts +++ b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts @@ -1,4 +1,4 @@ -import { Block } from "../../../domain/actions/aptos/PollAptos"; +import { Range } from "../../../domain/actions/aptos/PollAptos"; export function divideIntoBatches(set: Set, batchSize = 10): Set[] { const batches: Set[] = []; @@ -17,22 +17,3 @@ export function divideIntoBatches(set: Set, batchSize = 10): Set[] { } return batches; } - -export function createBatches(range: Block | undefined): number[] { - let batchSize = 100; - let total = 1; - - if (range && range.toBlock) { - batchSize = range.toBlock < batchSize ? range.toBlock : batchSize; - total = range.toBlock ?? total; - } - - const numBatches = Math.ceil(total / batchSize); - const batches: number[] = []; - - for (let i = 0; i < numBatches; i++) { - batches.push(batchSize); - } - - return batches; -} diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index d76311874..0d109383b 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -1,5 +1,10 @@ +import { + AptosBlockByVersion, + AptosEvent, + AptosTransactionByVersion, +} from "../../../domain/entities/aptos"; import { AptosClient } from "aptos"; -import { Block } from "../../../domain/actions/aptos/PollAptos"; +import { Range } from "../../../domain/actions/aptos/PollAptos"; import winston from "winston"; type InstrumentedAptosProviderOptions = Required> & @@ -31,25 +36,67 @@ export class InstrumentedAptosProvider { address: string, eventHandle: string, fieldName?: string, - fromBlock?: number, - toBlock: number = 100 - ): Promise { + from?: number, + limit: number = 100 + ): Promise { try { - const params = fromBlock ? { start: fromBlock, limit: toBlock } : { limit: toBlock }; + const params = from ? { start: from, limit: limit } : { limit: limit }; - const result = await this.client.getEventsByEventHandle( + const results = (await this.client.getEventsByEventHandle( address, eventHandle, fieldName!, params - ); - return result; + )) as EventsByEventHandle[]; + + // Create new AptosEvent objects with the necessary properties + const aptosEvents: AptosEvent[] = results.map((result) => ({ + guid: result.guid, + sequence_number: result.data.sequence, + type: result.type, + data: result.data, + version: result.version, + })); + + return aptosEvents; + } catch (e) { + throw e; + } + } + + public async getTransactions(block: Range): Promise { + try { + const params = block.from + ? { start: block.from, limit: block.limit } + : { limit: block.limit }; + + const results = await this.client.getTransactions(params); + + // Create new AptosEvent objects with the necessary properties + const aptosEvents = results + .map((result: AptosEventRepository) => { + if (result.events && result.events[0].guid) { + return { + version: result.version, + guid: result.events?.[0]?.guid!, + sequence_number: result.sequence_number!, + type: result.type, + data: result.data, + events: result.events, + hash: result.hash, + payload: result.payload, + }; + } + }) + .filter((x) => x !== undefined) as AptosEvent[]; + + return aptosEvents; } catch (e) { throw e; } } - public async getTransactionByVersion(version: number): Promise { + public async getTransactionByVersion(version: number): Promise { try { const result = await this.client.getTransactionByVersion(version); return result; @@ -70,7 +117,7 @@ export class InstrumentedAptosProvider { } } - public async getBlockByVersion(version: number): Promise { + public async getBlockByVersion(version: number): Promise { try { const result = await this.client.getBlockByVersion(version); return result; @@ -78,19 +125,6 @@ export class InstrumentedAptosProvider { throw e; } } - - public async getTransactions(block: Block): Promise { - try { - const params = block.fromBlock - ? { start: block.fromBlock, limit: block.toBlock } - : { limit: block.toBlock }; - - const result = await this.client.getTransactions(params); - return result; - } catch (e) { - throw e; - } - } } export type HttpClientOptions = { @@ -101,3 +135,24 @@ export type HttpClientOptions = { retries?: number; timeout?: number; }; + +type AptosEventRepository = { + sequence_number?: string; + timestamp?: string; + success?: boolean; + version?: string; + payload?: any; + events?: any[]; + sender?: string; + hash: string; + data?: any; + type: string; +}; + +type EventsByEventHandle = { + version?: string; + guid: any; + sequence_number: string; + type: string; + data: any; +}; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index 8a1cc37ac..9a675fb24 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -12,10 +12,8 @@ import { StatRepository, } from "../../../../src/domain/repositories"; -let getTransactionsByVersionsForSourceEventSpy: jest.SpiedFunction< - AptosRepository["getTransactionsByVersionForSourceEvent"] ->; -let getSequenceNumberSpy: jest.SpiedFunction; +let getTransactionsByVersionSpy: jest.SpiedFunction; +let getSequenceNumberSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; let handlerSpy: jest.SpiedFunction<(txs: AptosTransaction[]) => Promise>; @@ -32,8 +30,8 @@ let pollAptos: PollAptos; let props = { blockBatchSize: 100, - fromBlock: 0n, - toBlock: 0n, + from: 0n, + limit: 0n, environment: "testnet", commitment: "finalized", addresses: ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], @@ -58,7 +56,7 @@ describe("GetAptosSequences", () => { await pollAptos.stop(); }); - it("should be not generate range (fromBlock and toBlock) and search the latest block plus block batch size cfg", async () => { + it("should be not generate range (from and limit) and search the latest from plus from batch size cfg", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository(); @@ -73,7 +71,7 @@ describe("GetAptosSequences", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromBlock: undefined, toBlock: 100 }, + { from: undefined, limit: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -82,17 +80,17 @@ describe("GetAptosSequences", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionSpy).toHaveReturnedTimes(1) ); }); - it("should be use fromBlock and batch size cfg from cfg", async () => { + it("should be use from and batch size cfg from cfg", async () => { // Given givenAptosBlockRepository(); givenMetadataRepository(); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 146040n; + // Se from for cfg + props.from = 146040n; givenPollAptosTx(cfg); // When @@ -103,7 +101,7 @@ describe("GetAptosSequences", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromBlock: 146040, toBlock: 100 }, + { from: 146040, limit: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -112,17 +110,17 @@ describe("GetAptosSequences", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionSpy).toHaveReturnedTimes(1) ); }); - it("should be return the same lastBlock and toBlock equal 100", async () => { + it("should be return the same lastFrom and limit equal 100", async () => { // Given givenAptosBlockRepository(); - givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); + givenMetadataRepository({ previousFrom: 146040n, lastFrom: 146040n }); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 0n; + // Se from for cfg + props.from = 0n; givenPollAptosTx(cfg); // When @@ -133,7 +131,7 @@ describe("GetAptosSequences", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromBlock: 146040, toBlock: 100 }, + { from: 146040, limit: 1 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -142,17 +140,17 @@ describe("GetAptosSequences", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionSpy).toHaveReturnedTimes(1) ); }); - it("should be if return the lastBlock and toBlock equal the block batch size", async () => { + it("should be if return the lastFrom and limit equal the from batch size", async () => { // Given givenAptosBlockRepository(); - givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); + givenMetadataRepository({ previousFrom: undefined, lastFrom: 146040n }); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 0n; + // Se from for cfg + props.from = 0n; givenPollAptosTx(cfg); // When @@ -163,7 +161,7 @@ describe("GetAptosSequences", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { fromBlock: 146040, toBlock: 100 }, + { from: 146040, limit: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: @@ -172,7 +170,7 @@ describe("GetAptosSequences", () => { type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", } ), - () => expect(getTransactionsByVersionsForSourceEventSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsByVersionSpy).toHaveReturnedTimes(1) ); }); }); @@ -212,6 +210,7 @@ const givenAptosBlockRepository = () => { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", status: true, + to: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", events: [ { guid: { @@ -275,18 +274,48 @@ const givenAptosBlockRepository = () => { }, ]; + const aptosTxs = [ + { + guid: { creation_number: "3", account_address: "0x1" }, + type: "block_metadata_transaction", + events: [ + { + guid: { creation_number: "3", account_address: "0x1" }, + sequence_number: "156752466", + type: "0x1::block::NewBlockEvent", + data: { + epoch: "6236", + failed_proposer_indices: [], + hash: "0x245ec3db071a4e014e26f7a427cc5730c354dd621350b798770c9abef0fd6170", + height: "156752466", + previous_block_votes_bitvec: "0xffaa0150beef04c2e533d9fffdff373f", + proposer: "0x15d241369552ece871a16d865a4e9b96f5e6e6f8c7db5478e89af17845969c02", + round: "27858", + time_microseconds: "1710333535940198", + }, + }, + ], + sequence_number: "156752466", + hash: "0x7773df42233d4caad9206faf23487b19b382cdb21e28600b2df3a810eba0b968", + data: { + consistency_level: 1, + nonce: "123123", + payload: {}, + sender: "0x15d241369552ece871a16d865a4e9b96f5e6e6f8c7db5478e89af17845969c02", + sequence: "12312", + timestamp: "123123123", + }, + }, + ]; + aptosRepo = { - getSequenceNumber: () => Promise.resolve(events), - getTransactionsByVersionForSourceEvent: () => Promise.resolve(txs), - getTransactionsByVersionForRedeemedEvent: () => Promise.resolve(txs), - getTransactions: () => Promise.resolve(txs), + getEventsByEventHandle: () => Promise.resolve(events), + getTransactionsByVersion: () => Promise.resolve(txs), + getTransactions: () => Promise.resolve(aptosTxs), }; - getSequenceNumberSpy = jest.spyOn(aptosRepo, "getSequenceNumber"); - getTransactionsByVersionsForSourceEventSpy = jest.spyOn( - aptosRepo, - "getTransactionsByVersionForSourceEvent" - ); + getSequenceNumberSpy = jest.spyOn(aptosRepo, "getEventsByEventHandle"); + getTransactionsByVersionSpy = jest.spyOn(aptosRepo, "getTransactionsByVersion"); handlerSpy = jest.spyOn(handlers, "working"); }; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index e7784a168..e5a1e3f8d 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -12,9 +12,7 @@ import { StatRepository, } from "../../../../src/domain/repositories"; -let getTransactionsByVersionsForRedeemedEventSpy: jest.SpiedFunction< - AptosRepository["getTransactionsByVersionForSourceEvent"] ->; +let getTransactionsByVersionSpy: jest.SpiedFunction; let getTransactionsSpy: jest.SpiedFunction; let metadataSaveSpy: jest.SpiedFunction["save"]>; @@ -32,8 +30,8 @@ let pollAptos: PollAptos; let props = { blockBatchSize: 100, - fromBlock: 0n, - toBlock: 0n, + from: 0n, + limit: 0n, environment: "testnet", commitment: "finalized", addresses: ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], @@ -55,7 +53,7 @@ describe("GetAptosTransactions", () => { await pollAptos.stop(); }); - it("should be not generate range (fromBlock and toBlock) and search the latest block plus block batch size cfg, and not process tx because is not a wormhole redeem", async () => { + it("should be not generate range (from and limit) and search the latest from plus from batch size cfg, and not process tx because is not a wormhole redeem", async () => { // Given const tx = { version: "487572390", @@ -132,11 +130,11 @@ describe("GetAptosTransactions", () => { // Then await thenWaitForAssertion( () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: undefined, toBlock: 100 }) + () => expect(getTransactionsSpy).toBeCalledWith({ from: undefined, limit: 100 }) ); }); - it("should be use fromBlock and batch size cfg, and process tx because is a wormhole redeem", async () => { + it("should be use from and batch size cfg, and process tx because is a wormhole redeem", async () => { // Given const tx = { version: "487581688", @@ -274,8 +272,8 @@ describe("GetAptosTransactions", () => { givenAptosBlockRepository(tx); givenMetadataRepository(); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 146040n; + // Se from for cfg + props.from = 146040n; givenPollAptosTx(cfg); // Whem @@ -284,18 +282,18 @@ describe("GetAptosTransactions", () => { // Then await thenWaitForAssertion( () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }), - () => expect(getTransactionsByVersionsForRedeemedEventSpy).toHaveReturnedTimes(1) + () => expect(getTransactionsSpy).toBeCalledWith({ from: 146040, limit: 100 }), + () => expect(getTransactionsByVersionSpy).toHaveReturnedTimes(1) ); }); - it("should be return the same lastBlock and toBlock equal 1000", async () => { + it("should be return the same lastFrom and limit equal 1000", async () => { // Given givenAptosBlockRepository(); - givenMetadataRepository({ previousBlock: 146040n, lastBlock: 146040n }); + givenMetadataRepository({ previousFrom: 146040n, lastFrom: 146040n }); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 0n; + // Se from for cfg + props.from = 0n; givenPollAptosTx(cfg); // Whem @@ -304,17 +302,17 @@ describe("GetAptosTransactions", () => { // Then await thenWaitForAssertion( () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }) + () => expect(getTransactionsSpy).toBeCalledWith({ from: 146040, limit: 100 }) ); }); - it("should be if return the lastBlock and toBlock equal the block batch size", async () => { + it("should be if return the lastFrom and limit equal the block batch size", async () => { // Given givenAptosBlockRepository(); - givenMetadataRepository({ previousBlock: undefined, lastBlock: 146040n }); + givenMetadataRepository({ previousFrom: undefined, lastFrom: 146040n }); givenStatsRepository(); - // Se fromBlock for cfg - props.fromBlock = 0n; + // Se from for cfg + props.from = 0n; givenPollAptosTx(cfg); // Whem @@ -323,7 +321,7 @@ describe("GetAptosTransactions", () => { // Then await thenWaitForAssertion( () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ fromBlock: 146040, toBlock: 100 }) + () => expect(getTransactionsSpy).toBeCalledWith({ from: 146040, limit: 100 }) ); }); }); @@ -353,17 +351,13 @@ const givenAptosBlockRepository = (tx: any = {}) => { const txs = [tx]; aptosRepo = { - getSequenceNumber: () => Promise.resolve(events), - getTransactionsByVersionForSourceEvent: () => Promise.resolve([]), - getTransactionsByVersionForRedeemedEvent: () => Promise.resolve([]), + getEventsByEventHandle: () => Promise.resolve(events), + getTransactionsByVersion: () => Promise.resolve([]), getTransactions: () => Promise.resolve(txs), }; getTransactionsSpy = jest.spyOn(aptosRepo, "getTransactions"); - getTransactionsByVersionsForRedeemedEventSpy = jest.spyOn( - aptosRepo, - "getTransactionsByVersionForRedeemedEvent" - ); + getTransactionsByVersionSpy = jest.spyOn(aptosRepo, "getTransactionsByVersion"); handlerSpy = jest.spyOn(handlers, "working"); }; diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index 546558a62..9f47d49ac 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -104,13 +104,12 @@ txs = [ blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, - sequence: 3423n, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", - sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", status: true, + to: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", events: [ { guid: { diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index e298d323b..902f0028e 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -22,10 +22,8 @@ describe("aptosLogMessagePublishedMapper", () => { expect(result.attributes.payload).toBe( "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0" ); - expect(result.attributes.sender).toBe( - "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2" - ); - expect(result.attributes.sequence).toBe(3423); + expect(result.attributes.sender).toBe("1"); + expect(result.attributes.sequence).toBe(146094); } }); }); @@ -35,13 +33,12 @@ const txs: AptosTransaction = { blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, - sequence: 3423n, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", - sender: "0x5aa807666de4dd9901c8f14a2f6021e85dc792890ece7f6bb929b46dba7671a2", status: true, + to: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", events: [ { guid: { @@ -102,4 +99,5 @@ const txs: AptosTransaction = { ], nonce: 76704, hash: "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e", + type: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625::state::WormholeMessage", }; diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts index 56465c715..d5525f164 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts @@ -34,17 +34,34 @@ describe("aptosRedeemedTransactionFoundMapper", () => { // Given const tx: AptosTransaction = { consistencyLevel: 15, - emitterChain: 5, blockHeight: 154363203n, timestamp: 1709821894, blockTime: 1709821894, - sequence: 394768n, version: "487572005", - payload: - "010000000000000000000000000000000000000000000000000000000000989680069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f0000000000100019cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea00160000000000000000000000000000000000000000000000000000000000000000", + payload: { + function: + "0xb1421c3d524a353411aa4e3cce0f0ce7f404a12da91a2889e1bc3cea6ffb17da::cancel_and_place::cancel_and_place", + type_arguments: [ + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::WETH", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC", + ], + arguments: [ + "0x01000000030d010f845d9923156821372114d23c633a86d8651cf79524764555dc9711631de5a4623f6cec4127b1e6872532bd24c148e326d324c09e413dc0ca222ec3dbc56abd00035fe16475cbf0d4f1a07f76dffd9690dc56fdeee3dce8f15f0dc4c71e32964ef72cb8b3b41c042912622dfb0de69e92dc77387f51659fdf85d7783b9cc8c0662d0104f982da1feb8b0b6a33ab69e395bda21dc1125fa1bae8386174c989f921f2bd71794ebaca92fed4c92be13cc031af8c27678b65ae2a2bf891b689ab15050bf62b000696f6ec7f68b7a0bf659f541a02e98a8c6e054b9ed7c3b654d97e3db678efa0f500465d833d307e23da40cbbc52e42457bb1d52e36a5b44b557cc84a970ae4ee40107097002812bea42577b5ba3c570a4b7f580a89144d2941aa699787eeaebaffb027d7ba89fd01ce2d635356ba63a7b25fd880b6e033a69717641ceabeb0fd2df2a0008e0faeafb1e8b2bd0dcaf807e7185ec2c425725e53cf933f713ac3e1461a712400b004d913c07d97d5fa2e7aed8a42cf3f3a7bca1fa74978653dcb235a5ba8eb60109610dd1508c204ff306584dba43a8bd86bc7c9fabefae661f367d4a01fd82bdfb5e5ed9a72cc4b0a5d12f6a47de5e6b66c3498afbb3b242012355ae5a1658a0ab010a7bb4707ee2af704ac0de725bc7c3ba9f776a595244d21203a05211cdfbfc550e5281a16d3f9f5d691a4b26a11af22a682b8f317c2cda55bde9272d2d68f9ed2e010c85d6087988bed609680b2047820bf14fca38001d2702607fe5f50a05a2b9533f2460be246d1c249e90aac396b06aaecb90304b9c4748d3ff6280a48338f7f4fc000df65098fbae3333d0dbaef9b10c50f2ec0d395e899d939c1a91363fa570d711e955d20eec3c7fba59f8198ad2ef5746a9a21ff69441c2b460ad4e8a55f3fb21450010493b06f48838dcf965e08bd266473d4275c5b3b73e70ca03fbeb198f5d610e955c6d11f16b9e592e0f85fe6d1f7f82118e6933541bb90dd20ad41c884a0308a4011123515dc5e8222adec3c31130bd245e43a4a7b435bb531027f7632fcd23607a92592d0769557558686f5cc8ba606096ea181829bc0d54e36a4d249a59f74034e50012db6bf0b9aad11fbb5959128a1103dc5d948d6ede23359626700d1b2c6dff9be1137888f72bd0b79fd7c36e511c05c1475a837954948e313b981e6c56123d1d270165f0be060000410f0015ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5000000000001b50b00010000000000000000000000000000000000000000000000000000000004d13720000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000234226de67ee40839dff5760c2725bd2d5d7817fea499126c533e69f5702c5a7d00160000000000000000000000000000000000000000000000000000000000000000", + ["42645072269700382266349876", "42644869357063623703648693"], + ["42645090718696431883245683", "42644906249285249691670581"], + ["2517", "10041"], + ["383120", "383216"], + ["2539", "10008"], + ["382928", "382832"], + "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + 3, + 0, + ], + type: "entry_function_payload", + }, address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", - sender: "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde", status: true, + to: "0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea", events: [], nonce: 302448640, hash: "0xd297f46372ed734fd8f3b595a898f42ab328a4e3cc9ce0f810c6c66e64873e64", @@ -60,17 +77,22 @@ describe("aptosRedeemedTransactionFoundMapper", () => { const tx: AptosTransaction = { consistencyLevel: 15, - emitterChain: 5, blockHeight: 154363203n, timestamp: 1709821894, blockTime: 1709821894, - sequence: 394768n, version: "487572005", - payload: - "010000000000000000000000000000000000000000000000000000000000989680069b8857feab8184fb687f634618c035dac439dc1aeb3b5598a0f0000000000100019cb6a1e8b0e7104e988b8d5928d58f79995b7d8832a873017bfc2038037768ea00160000000000000000000000000000000000000000000000000000000000000000", + payload: { + function: + "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f::complete_transfer::submit_vaa_and_register_entry", + type_arguments: ["0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea::coin::T"], + arguments: [ + "0x01000000030d010f845d9923156821372114d23c633a86d8651cf79524764555dc9711631de5a4623f6cec4127b1e6872532bd24c148e326d324c09e413dc0ca222ec3dbc56abd00035fe16475cbf0d4f1a07f76dffd9690dc56fdeee3dce8f15f0dc4c71e32964ef72cb8b3b41c042912622dfb0de69e92dc77387f51659fdf85d7783b9cc8c0662d0104f982da1feb8b0b6a33ab69e395bda21dc1125fa1bae8386174c989f921f2bd71794ebaca92fed4c92be13cc031af8c27678b65ae2a2bf891b689ab15050bf62b000696f6ec7f68b7a0bf659f541a02e98a8c6e054b9ed7c3b654d97e3db678efa0f500465d833d307e23da40cbbc52e42457bb1d52e36a5b44b557cc84a970ae4ee40107097002812bea42577b5ba3c570a4b7f580a89144d2941aa699787eeaebaffb027d7ba89fd01ce2d635356ba63a7b25fd880b6e033a69717641ceabeb0fd2df2a0008e0faeafb1e8b2bd0dcaf807e7185ec2c425725e53cf933f713ac3e1461a712400b004d913c07d97d5fa2e7aed8a42cf3f3a7bca1fa74978653dcb235a5ba8eb60109610dd1508c204ff306584dba43a8bd86bc7c9fabefae661f367d4a01fd82bdfb5e5ed9a72cc4b0a5d12f6a47de5e6b66c3498afbb3b242012355ae5a1658a0ab010a7bb4707ee2af704ac0de725bc7c3ba9f776a595244d21203a05211cdfbfc550e5281a16d3f9f5d691a4b26a11af22a682b8f317c2cda55bde9272d2d68f9ed2e010c85d6087988bed609680b2047820bf14fca38001d2702607fe5f50a05a2b9533f2460be246d1c249e90aac396b06aaecb90304b9c4748d3ff6280a48338f7f4fc000df65098fbae3333d0dbaef9b10c50f2ec0d395e899d939c1a91363fa570d711e955d20eec3c7fba59f8198ad2ef5746a9a21ff69441c2b460ad4e8a55f3fb21450010493b06f48838dcf965e08bd266473d4275c5b3b73e70ca03fbeb198f5d610e955c6d11f16b9e592e0f85fe6d1f7f82118e6933541bb90dd20ad41c884a0308a4011123515dc5e8222adec3c31130bd245e43a4a7b435bb531027f7632fcd23607a92592d0769557558686f5cc8ba606096ea181829bc0d54e36a4d249a59f74034e50012db6bf0b9aad11fbb5959128a1103dc5d948d6ede23359626700d1b2c6dff9be1137888f72bd0b79fd7c36e511c05c1475a837954948e313b981e6c56123d1d270165f0be060000410f0015ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5000000000001b50b00010000000000000000000000000000000000000000000000000000000004d13720000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000234226de67ee40839dff5760c2725bd2d5d7817fea499126c533e69f5702c5a7d00160000000000000000000000000000000000000000000000000000000000000000", + ], + type: "entry_function_payload", + }, address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", - sender: "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde", status: true, + to: "0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea", events: [ { guid: { From 3e5fafcedadd5a7a571adb4024bc9c81a951ddd2 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 15:20:16 -0300 Subject: [PATCH 21/30] Improve code --- .../domain/actions/aptos/GetAptosSequences.ts | 13 ++++----- .../actions/aptos/GetAptosTransactions.ts | 6 ++--- .../aptos/AptosJsonRPCBlockRepository.ts | 10 +++---- .../repositories/common/utils.ts | 2 -- .../rpc/http/InstrumentedAptosProvider.ts | 27 +++++-------------- 5 files changed, 21 insertions(+), 37 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts index d929ebdec..b4000dc14 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts @@ -22,11 +22,12 @@ export class GetAptosSequences { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const incrementBatchIndex = 100; - const limitBatch = opts.previousFrom - ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 - : 100; - let limit = limitBatch < 100 ? 1 : 100; + const batchIndex = 100; + const limitBatch = + opts.previousFrom && opts.lastFrom + ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 + : batchIndex; + let limit = limitBatch < batchIndex ? 1 : batchIndex; while (limit <= limitBatch) { const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; @@ -55,7 +56,7 @@ export class GetAptosSequences { populatedTransactions.push(tx); }); - limit += incrementBatchIndex; + limit += batchIndex; } this.logger.info( diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 4dbaa746b..23e2c9337 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -22,8 +22,8 @@ export class GetAptosTransactions { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const incrementBatchIndex = 100; - const limitBatch = range?.limit ?? 100; + const batchIndex = 100; + const limitBatch = range?.limit ?? batchIndex; let limit = 100; while (limit <= limitBatch) { @@ -60,7 +60,7 @@ export class GetAptosTransactions { }); } - limit += incrementBatchIndex; + limit += batchIndex; } return populatedTransactions; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 0e6e67b46..7b130a1ec 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -16,15 +16,15 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { filter: TransactionFilter ): Promise { try { - const fromBlock = range?.from ? Number(range?.from) : undefined; - const toSequence = range?.limit ? Number(range?.limit) : undefined; + const from = range?.from ? Number(range?.from) : undefined; + const limit = range?.limit ? Number(range?.limit) : undefined; const results = await this.client.getEventsByEventHandle( filter.address, filter.event!, filter.fieldName, - fromBlock, - toSequence + from, + limit ); return results; @@ -61,7 +61,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { return transactions; } catch (e) { - this.handleError(e, "getTransactionsByVersionForSourceEvent"); + this.handleError(e, "getTransactionsByVersion"); throw e; } } diff --git a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts index 6e70b006d..780735681 100644 --- a/blockchain-watcher/src/infrastructure/repositories/common/utils.ts +++ b/blockchain-watcher/src/infrastructure/repositories/common/utils.ts @@ -1,5 +1,3 @@ -import { Range } from "../../../domain/actions/aptos/PollAptos"; - export function divideIntoBatches(set: Set, batchSize = 10): Set[] { const batches: Set[] = []; let batch: any[] = []; diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index 0d109383b..5196bb534 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -1,11 +1,10 @@ +import { AptosClient } from "aptos"; +import { Range } from "../../../domain/actions/aptos/PollAptos"; import { + AptosTransactionByVersion, AptosBlockByVersion, AptosEvent, - AptosTransactionByVersion, } from "../../../domain/entities/aptos"; -import { AptosClient } from "aptos"; -import { Range } from "../../../domain/actions/aptos/PollAptos"; -import winston from "winston"; type InstrumentedAptosProviderOptions = Required> & HttpClientOptions; @@ -18,8 +17,6 @@ export class InstrumentedAptosProvider { private url: string; client: AptosClient; - private logger: winston.Logger = winston.child({ module: "InstrumentedAptosProvider" }); - constructor(options: InstrumentedAptosProviderOptions) { options?.initialDelay && (this.initialDelay = options.initialDelay); options?.maxDelay && (this.maxDelay = options.maxDelay); @@ -40,7 +37,7 @@ export class InstrumentedAptosProvider { limit: number = 100 ): Promise { try { - const params = from ? { start: from, limit: limit } : { limit: limit }; + const params = from ? { start: from, limit } : { limit }; const results = (await this.client.getEventsByEventHandle( address, @@ -49,7 +46,7 @@ export class InstrumentedAptosProvider { params )) as EventsByEventHandle[]; - // Create new AptosEvent objects with the necessary properties + // Mapped to AptosEvent internal entity const aptosEvents: AptosEvent[] = results.map((result) => ({ guid: result.guid, sequence_number: result.data.sequence, @@ -72,7 +69,7 @@ export class InstrumentedAptosProvider { const results = await this.client.getTransactions(params); - // Create new AptosEvent objects with the necessary properties + // Mapped to AptosEvent internal entity const aptosEvents = results .map((result: AptosEventRepository) => { if (result.events && result.events[0].guid) { @@ -105,18 +102,6 @@ export class InstrumentedAptosProvider { } } - public async getBlockByHeight( - blockHeight: number, - withTransactions?: boolean | undefined - ): Promise { - try { - const result = await this.client.getBlockByHeight(blockHeight, withTransactions); - return result; - } catch (e) { - throw e; - } - } - public async getBlockByVersion(version: number): Promise { try { const result = await this.client.getBlockByVersion(version); From 879e150373d739fd5550448b5640df7c0f994d4d Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 16:05:37 -0300 Subject: [PATCH 22/30] Resolve comment in PR --- .../actions/aptos/GetAptosTransactions.ts | 22 ++++++++++---- ...ces.ts => GetAptosTransactionsByEvents.ts} | 30 ++++++++++++------- .../src/domain/actions/aptos/PollAptos.ts | 8 ++--- .../src/domain/entities/aptos.ts | 11 +++++++ blockchain-watcher/src/domain/repositories.ts | 6 ++-- .../aptos/AptosJsonRPCBlockRepository.ts | 10 +++++-- .../rpc/http/InstrumentedAptosProvider.ts | 7 +++-- .../actions/aptos/GetAptosSequences.test.ts | 10 +++++-- 8 files changed, 73 insertions(+), 31 deletions(-) rename blockchain-watcher/src/domain/actions/aptos/{GetAptosSequences.ts => GetAptosTransactionsByEvents.ts} (85%) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 23e2c9337..e3f6cf044 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -22,16 +22,14 @@ export class GetAptosTransactions { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const batchIndex = 100; - const limitBatch = range?.limit ?? batchIndex; - let limit = 100; + let { batchSize, totalBatchLimit, limitBatch } = this.createBatch(range); - while (limit <= limitBatch) { + while (limitBatch <= totalBatchLimit) { const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; const transactions = await this.repo.getTransactions({ from: fromBatch, - limit: limit, + limit: limitBatch, }); // Only process transactions to the contract address configured @@ -60,7 +58,7 @@ export class GetAptosTransactions { }); } - limit += batchIndex; + limitBatch += batchSize; } return populatedTransactions; @@ -107,4 +105,16 @@ export class GetAptosTransactions { lastFrom: this.lastFrom, }; } + + private createBatch(range: Range | undefined) { + const batchSize = 100; + const totalBatchLimit = range?.limit ?? batchSize; + let limitBatch = 100; + + return { + batchSize, + totalBatchLimit, + limitBatch, + }; + } } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts similarity index 85% rename from blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts rename to blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts index b4000dc14..70fc15b0d 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosSequences.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts @@ -3,7 +3,7 @@ import { AptosTransaction } from "../../entities/aptos"; import { AptosRepository } from "../../repositories"; import winston from "winston"; -export class GetAptosSequences { +export class GetAptosTransactionsByEvents { protected readonly logger: winston.Logger; private readonly repo: AptosRepository; @@ -11,7 +11,7 @@ export class GetAptosSequences { private lastFrom?: bigint; constructor(repo: AptosRepository) { - this.logger = winston.child({ module: "GetAptosSequences" }); + this.logger = winston.child({ module: "GetAptosTransactionsByEvents" }); this.repo = repo; } @@ -22,14 +22,9 @@ export class GetAptosSequences { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const batchIndex = 100; - const limitBatch = - opts.previousFrom && opts.lastFrom - ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 - : batchIndex; - let limit = limitBatch < batchIndex ? 1 : batchIndex; + let { batchSize, totalBatchLimit, limitBatch } = this.createBatch(opts); - while (limit <= limitBatch) { + while (limitBatch <= totalBatchLimit) { const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; const events = await this.repo.getEventsByEventHandle( @@ -56,7 +51,7 @@ export class GetAptosSequences { populatedTransactions.push(tx); }); - limit += batchIndex; + limitBatch += batchSize; } this.logger.info( @@ -112,4 +107,19 @@ export class GetAptosSequences { lastFrom: this.lastFrom, }; } + + private createBatch(opts: GetAptosOpts) { + const batchSize = 100; + const totalBatchLimit = + opts.previousFrom && opts.lastFrom + ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 + : batchSize; + let limitBatch = totalBatchLimit < batchSize ? 1 : batchSize; + + return { + batchSize, + totalBatchLimit, + limitBatch, + }; + } } diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index a20d9b20f..82c1cae75 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -1,18 +1,18 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; import { GetAptosTransactions } from "./GetAptosTransactions"; -import { GetAptosSequences } from "./GetAptosSequences"; +import { GetAptosTransactionsByEvents } from "./GetAptosTransactionsByEvents"; import { AptosTransaction } from "../../entities/aptos"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; export class PollAptos extends RunPollingJob { protected readonly logger: Logger; - private readonly getAptos: GetAptosSequences; + private readonly getAptos: GetAptosTransactionsByEvents; private previousFrom?: bigint; private lastFrom?: bigint; private getAptosRecords: { [key: string]: any } = { - GetAptosSequences, + GetAptosTransactionsByEvents, GetAptosTransactions, }; @@ -25,7 +25,7 @@ export class PollAptos extends RunPollingJob { ) { super(cfg.id, statsRepo, cfg.interval); this.logger = winston.child({ module: "PollAptos", label: this.cfg.id }); - this.getAptos = new this.getAptosRecords[getAptos ?? "GetAptosSequences"](repo); + this.getAptos = new this.getAptosRecords[getAptos ?? "GetAptosTransactionsByEvents"](repo); } protected async preHook(): Promise { diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 2e031aad2..46974f524 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -33,6 +33,17 @@ export type AptosTransaction = { to: string; }; +export type AptosTransactionByRange = { + hash?: string; + events?: any; + timestamp?: string; + version?: string; + payload?: any; + data: any; + sequence_number: string; + type: string; +}; + export type AptosTransactionByVersion = { sequence_number?: string; timestamp?: string; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index b08d9c740..ff14596d1 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -19,7 +19,7 @@ import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { TransactionFilter } from "./actions/aptos/PollAptos"; -import { AptosEvent, AptosTransaction } from "./entities/aptos"; +import { AptosEvent, AptosTransaction, AptosTransactionByRange } from "./entities/aptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -72,13 +72,13 @@ export interface SuiRepository { export interface AptosRepository { getTransactions( range: { from?: number | undefined; limit?: number | undefined } | undefined - ): Promise; + ): Promise; getEventsByEventHandle( range: { from?: number | undefined; limit?: number | undefined } | undefined, filter: TransactionFilter ): Promise; getTransactionsByVersion( - events: AptosEvent[], + events: AptosEvent[] | AptosTransactionByRange[], filter: TransactionFilter ): Promise; } diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 7b130a1ec..613722e03 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,4 +1,8 @@ -import { AptosEvent, AptosTransaction } from "../../../domain/entities/aptos"; +import { + AptosEvent, + AptosTransaction, + AptosTransactionByRange, +} from "../../../domain/entities/aptos"; import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; import { Range, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; import { AptosRepository } from "../../../domain/repositories"; @@ -35,7 +39,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { } async getTransactionsByVersion( - events: AptosEvent[], + events: AptosEvent[] | AptosTransactionByRange[], filter: TransactionFilter ): Promise { try { @@ -66,7 +70,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { } } - async getTransactions(range: Range): Promise { + async getTransactions(range: Range): Promise { try { const results = await this.client.getTransactions(range); return results; diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts index 5196bb534..54a94be2f 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts @@ -2,6 +2,7 @@ import { AptosClient } from "aptos"; import { Range } from "../../../domain/actions/aptos/PollAptos"; import { AptosTransactionByVersion, + AptosTransactionByRange, AptosBlockByVersion, AptosEvent, } from "../../../domain/entities/aptos"; @@ -61,7 +62,7 @@ export class InstrumentedAptosProvider { } } - public async getTransactions(block: Range): Promise { + public async getTransactions(block: Range): Promise { try { const params = block.from ? { start: block.from, limit: block.limit } @@ -69,7 +70,7 @@ export class InstrumentedAptosProvider { const results = await this.client.getTransactions(params); - // Mapped to AptosEvent internal entity + // Mapped to AptosTransactionByRange internal entity const aptosEvents = results .map((result: AptosEventRepository) => { if (result.events && result.events[0].guid) { @@ -85,7 +86,7 @@ export class InstrumentedAptosProvider { }; } }) - .filter((x) => x !== undefined) as AptosEvent[]; + .filter((x) => x !== undefined) as AptosTransactionByRange[]; return aptosEvents; } catch (e) { diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index 9a675fb24..dadd89351 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -51,7 +51,7 @@ let props = { let cfg = new PollAptosTransactionsConfig(props); -describe("GetAptosSequences", () => { +describe("GetAptosTransactionsByEvents", () => { afterEach(async () => { await pollAptos.stop(); }); @@ -336,7 +336,13 @@ const givenStatsRepository = () => { }; const givenPollAptosTx = (cfg: PollAptosTransactionsConfig) => { - pollAptos = new PollAptos(cfg, statsRepo, metadataRepo, aptosRepo, "GetAptosSequences"); + pollAptos = new PollAptos( + cfg, + statsRepo, + metadataRepo, + aptosRepo, + "GetAptosTransactionsByEvents" + ); }; const whenPollEvmLogsStarts = async () => { From 83d3fab13160f9e30890b47860530410b5042d72 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 17:44:18 -0300 Subject: [PATCH 23/30] Integrate rpc poll for aptos --- blockchain-watcher/config/mainnet.json | 2 +- .../aptos/GetAptosTransactionsByEvents.ts | 4 +- .../src/domain/entities/aptos.ts | 4 +- .../repositories/RepositoriesBuilder.ts | 18 ++++-- .../aptos/AptosJsonRPCBlockRepository.ts | 61 ++++++++++++------- .../rpc/http/InstrumentedHttpProvider.ts | 33 +++++++++- .../actions/aptos/GetAptosSequences.test.ts | 2 +- 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/blockchain-watcher/config/mainnet.json b/blockchain-watcher/config/mainnet.json index c8c5eb2ad..0253a072e 100644 --- a/blockchain-watcher/config/mainnet.json +++ b/blockchain-watcher/config/mainnet.json @@ -121,7 +121,7 @@ }, "aptos": { "network": "mainnet", - "rpcs": ["https://fullnode.mainnet.aptoslabs.com"] + "rpcs": ["https://fullnode.mainnet.aptoslabs.com/v1"] } } } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts index 70fc15b0d..4e505d9e1 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts @@ -30,7 +30,7 @@ export class GetAptosTransactionsByEvents { const events = await this.repo.getEventsByEventHandle( { from: fromBatch, - limit: limitBatch, + limit: batchSize, }, opts.filter ); @@ -112,7 +112,7 @@ export class GetAptosTransactionsByEvents { const batchSize = 100; const totalBatchLimit = opts.previousFrom && opts.lastFrom - ? Number(opts.previousFrom) - Number(opts.lastFrom) + 1 + ? Number(opts.lastFrom) - Number(opts.previousFrom) + 1 : batchSize; let limitBatch = totalBatchLimit < batchSize ? 1 : batchSize; diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 46974f524..3ae8c2534 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -52,9 +52,9 @@ export type AptosTransactionByVersion = { payload?: any; events?: any[]; sender?: string; - hash: string; + hash?: string; }; export type AptosBlockByVersion = { - block_height: string; + block_height?: string; }; diff --git a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts index b869ba311..ffb5076e4 100644 --- a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts +++ b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts @@ -2,7 +2,6 @@ import { AptosRepository, JobRepository, SuiRepository } from "../../domain/repo import { RateLimitedAptosJsonRPCBlockRepository } from "./aptos/RateLimitedAptosJsonRPCBlockRepository"; import { RateLimitedEvmJsonRPCBlockRepository } from "./evm/RateLimitedEvmJsonRPCBlockRepository"; import { RateLimitedSuiJsonRPCBlockRepository } from "./sui/RateLimitedSuiJsonRPCBlockRepository"; -import { AptosJsonRPCBlockRepository } from "./aptos/AptosJsonRPCBlockRepository"; import { SNSClient, SNSClientConfig } from "@aws-sdk/client-sns"; import { InstrumentedAptosProvider } from "../rpc/http/InstrumentedAptosProvider"; import { InstrumentedHttpProvider } from "../rpc/http/InstrumentedHttpProvider"; @@ -29,6 +28,7 @@ import { SnsEventRepository, ProviderPoolMap, } from "."; +import { AptosJsonRPCBlockRepository } from "./aptos/AptosJsonRPCBlockRepository"; const SOLANA_CHAIN = "solana"; const APTOS_CHAIN = "aptos"; @@ -141,10 +141,10 @@ export class RepositoriesBuilder { } if (chain === APTOS_CHAIN) { + const pools = this.createAptosProviderPools(); + const aptosRepository = new RateLimitedAptosJsonRPCBlockRepository( - new AptosJsonRPCBlockRepository( - this.createAptosClient(chain, this.cfg.chains[chain].rpcs[0]) - ) + new AptosJsonRPCBlockRepository(pools) ); this.repositories.set("aptos-repo", aptosRepository); @@ -241,6 +241,16 @@ export class RepositoriesBuilder { return pools; } + private createAptosProviderPools() { + const cfg = this.cfg.chains[APTOS_CHAIN]; + const pools = providerPoolSupplier( + cfg.rpcs.map((url) => ({ url })), + (rpcCfg: RpcConfig) => this.createHttpClient(APTOS_CHAIN, rpcCfg.url), + POOL_STRATEGY + ); + return pools; + } + private createHttpClient(chain: string, url: string): InstrumentedHttpProvider { return new InstrumentedHttpProvider({ chain, diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 613722e03..9553903df 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -1,18 +1,25 @@ -import { - AptosEvent, - AptosTransaction, - AptosTransactionByRange, -} from "../../../domain/entities/aptos"; -import { InstrumentedAptosProvider } from "../../rpc/http/InstrumentedAptosProvider"; import { Range, TransactionFilter } from "../../../domain/actions/aptos/PollAptos"; +import { InstrumentedHttpProvider } from "../../rpc/http/InstrumentedHttpProvider"; import { AptosRepository } from "../../../domain/repositories"; +import { ProviderPool } from "@xlabs/rpc-pool"; import winston from "winston"; +import { + AptosTransactionByVersion, + AptosTransactionByRange, + AptosBlockByVersion, + AptosTransaction, + AptosEvent, +} from "../../../domain/entities/aptos"; + +type ProviderPoolMap = ProviderPool; export class AptosJsonRPCBlockRepository implements AptosRepository { private readonly logger: winston.Logger; + protected pool: ProviderPoolMap; - constructor(private readonly client: InstrumentedAptosProvider) { + constructor(pool: ProviderPool) { this.logger = winston.child({ module: "AptosJsonRPCBlockRepository" }); + this.pool = pool; } async getEventsByEventHandle( @@ -20,16 +27,14 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { filter: TransactionFilter ): Promise { try { + let results: AptosEvent[] = []; + const from = range?.from ? Number(range?.from) : undefined; const limit = range?.limit ? Number(range?.limit) : undefined; - const results = await this.client.getEventsByEventHandle( - filter.address, - filter.event!, - filter.fieldName, - from, - limit - ); + const endpoint = `/accounts/${filter.address}/events/${filter.event}/${filter.fieldName}?start=${from}&limit=${limit}`; + + results = await this.pool.get().get({ endpoint }); return results; } catch (e) { @@ -45,19 +50,25 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { try { const transactions = await Promise.all( events.map(async (event) => { - const transaction = await this.client.getTransactionByVersion(Number(event.version)); - const block = await this.client.getBlockByVersion(Number(event.version)); + const txEndpoint = `/transactions/by_version/${Number(event.version)}`; + const blockEndpoint = `/blocks/by_version/${Number(event.version)}`; + + let txResult: AptosTransactionByVersion = {}; + let blockResult: AptosBlockByVersion = {}; + + txResult = await this.pool.get().get({ endpoint: txEndpoint }); + blockResult = await this.pool.get().get({ endpoint: blockEndpoint }); return { consistencyLevel: event?.data?.consistency_level, - blockHeight: BigInt(block.block_height), - version: transaction.version!, + blockHeight: BigInt(blockResult.block_height!), + version: txResult.version!, address: event.events ? event.events[0].guid.account_address : filter.address, - status: transaction.success, - events: transaction.events, - hash: transaction.hash, + status: txResult.success, + events: txResult.events, + hash: txResult.hash!, type: filter.type, - payload: transaction.payload, + payload: txResult.payload, to: filter.address, }; }) @@ -72,7 +83,11 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { async getTransactions(range: Range): Promise { try { - const results = await this.client.getTransactions(range); + let results: AptosTransactionByRange[] = []; + + const endpoint = `?start=${range.from}&limit=${range.limit}`; + results = await this.pool.get().get({ endpoint }); + return results; } catch (e) { this.handleError(e, "getTransactions"); diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts index 343f43b75..277c0ff8e 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts @@ -37,10 +37,10 @@ export class InstrumentedHttpProvider { } public async post(body: any, opts?: HttpClientOptions): Promise { - return this.execute("POST", body, opts); + return this.executePost("POST", body, opts); } - private async execute(method: string, body?: any, opts?: HttpClientOptions): Promise { + private async executePost(method: string, body?: any, opts?: HttpClientOptions): Promise { let response; try { response = await this.health.fetch(this.url, { @@ -72,6 +72,35 @@ export class InstrumentedHttpProvider { return response.json() as T; } + + public async get(params: any, opts?: HttpClientOptions): Promise { + return this.executeGet("GET", params, opts); + } + + private async executeGet(method: string, params?: any, opts?: HttpClientOptions): Promise { + let response; + try { + response = await this.health.fetch(`${this.url}${params.endpoint}`, { + method: method, + signal: AbortSignal.timeout(opts?.timeout ?? this.timeout), + headers: { + "Content-Type": "application/json", + }, + }); + } catch (e: AxiosError | any) { + // Connection / timeout error: + if (e instanceof AxiosError) { + throw new HttpClientError(e.message ?? e.code, { status: e?.status ?? 0 }, e); + } + + throw new HttpClientError(e.message ?? e.code, undefined, e); + } + + if (!(response.status > 200) && !(response.status < 300)) { + throw new HttpClientError(undefined, response, response.json()); + } + return response.json() as T; + } } export type HttpClientOptions = { diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index dadd89351..739b655a0 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -131,7 +131,7 @@ describe("GetAptosTransactionsByEvents", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { from: 146040, limit: 1 }, + { from: 146040, limit: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: From bf4aedb95e8fecaf60f44ba06ccf9c95d5657298 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 17:52:02 -0300 Subject: [PATCH 24/30] Improve code --- .../src/domain/actions/aptos/GetAptosTransactions.ts | 2 +- .../repositories/aptos/AptosJsonRPCBlockRepository.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index e3f6cf044..058bd58d7 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -29,7 +29,7 @@ export class GetAptosTransactions { const transactions = await this.repo.getTransactions({ from: fromBatch, - limit: limitBatch, + limit: batchSize, }); // Only process transactions to the contract address configured diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 9553903df..70aadc3a9 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -85,7 +85,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { try { let results: AptosTransactionByRange[] = []; - const endpoint = `?start=${range.from}&limit=${range.limit}`; + const endpoint = `/transactions?start=${range.from}&limit=${range.limit}`; results = await this.pool.get().get({ endpoint }); return results; From 44d4417d13e276078403d2d1426103222ede390a Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 18:01:28 -0300 Subject: [PATCH 25/30] Remove instrumented aptos client --- .../src/domain/actions/aptos/PollAptos.ts | 2 +- .../aptosRedeemedTransactionFoundMapper.ts | 1 + .../repositories/RepositoriesBuilder.ts | 12 -- .../rpc/http/InstrumentedAptosProvider.ts | 144 ------------------ 4 files changed, 2 insertions(+), 157 deletions(-) delete mode 100644 blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index 82c1cae75..d04d5a32a 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -1,6 +1,6 @@ import { AptosRepository, MetadataRepository, StatRepository } from "../../repositories"; -import { GetAptosTransactions } from "./GetAptosTransactions"; import { GetAptosTransactionsByEvents } from "./GetAptosTransactionsByEvents"; +import { GetAptosTransactions } from "./GetAptosTransactions"; import { AptosTransaction } from "../../entities/aptos"; import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index b166c9a6c..bc490b537 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -16,6 +16,7 @@ export const aptosRedeemedTransactionFoundMapper = ( const vaaBuffer = Buffer.from(tx.payload?.arguments[0]?.substring(2), "hex"); const vaa = parseVaa(vaaBuffer); + const emitterAddress = vaa.emitterAddress.toString("hex"); if (protocol && protocol.type && protocol.method) { diff --git a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts index ffb5076e4..cfa42cacd 100644 --- a/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts +++ b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts @@ -3,7 +3,6 @@ import { RateLimitedAptosJsonRPCBlockRepository } from "./aptos/RateLimitedAptos import { RateLimitedEvmJsonRPCBlockRepository } from "./evm/RateLimitedEvmJsonRPCBlockRepository"; import { RateLimitedSuiJsonRPCBlockRepository } from "./sui/RateLimitedSuiJsonRPCBlockRepository"; import { SNSClient, SNSClientConfig } from "@aws-sdk/client-sns"; -import { InstrumentedAptosProvider } from "../rpc/http/InstrumentedAptosProvider"; import { InstrumentedHttpProvider } from "../rpc/http/InstrumentedHttpProvider"; import { Config } from "../config"; import { @@ -261,15 +260,4 @@ export class RepositoriesBuilder { maxDelay: 30_000, }); } - - private createAptosClient(chain: string, url: string): InstrumentedAptosProvider { - return new InstrumentedAptosProvider({ - chain, - url, - retries: 3, - timeout: 1_0000, - initialDelay: 1_000, - maxDelay: 30_000, - }); - } } diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts deleted file mode 100644 index 54a94be2f..000000000 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedAptosProvider.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { AptosClient } from "aptos"; -import { Range } from "../../../domain/actions/aptos/PollAptos"; -import { - AptosTransactionByVersion, - AptosTransactionByRange, - AptosBlockByVersion, - AptosEvent, -} from "../../../domain/entities/aptos"; - -type InstrumentedAptosProviderOptions = Required> & - HttpClientOptions; - -export class InstrumentedAptosProvider { - private initialDelay: number = 1_000; - private maxDelay: number = 60_000; - private retries: number = 0; - private timeout: number = 5_000; - private url: string; - client: AptosClient; - - constructor(options: InstrumentedAptosProviderOptions) { - options?.initialDelay && (this.initialDelay = options.initialDelay); - options?.maxDelay && (this.maxDelay = options.maxDelay); - options?.retries && (this.retries = options.retries); - options?.timeout && (this.timeout = options.timeout); - - if (!options.url) throw new Error("URL is required"); - this.url = options.url; - - this.client = new AptosClient(this.url); - } - - public async getEventsByEventHandle( - address: string, - eventHandle: string, - fieldName?: string, - from?: number, - limit: number = 100 - ): Promise { - try { - const params = from ? { start: from, limit } : { limit }; - - const results = (await this.client.getEventsByEventHandle( - address, - eventHandle, - fieldName!, - params - )) as EventsByEventHandle[]; - - // Mapped to AptosEvent internal entity - const aptosEvents: AptosEvent[] = results.map((result) => ({ - guid: result.guid, - sequence_number: result.data.sequence, - type: result.type, - data: result.data, - version: result.version, - })); - - return aptosEvents; - } catch (e) { - throw e; - } - } - - public async getTransactions(block: Range): Promise { - try { - const params = block.from - ? { start: block.from, limit: block.limit } - : { limit: block.limit }; - - const results = await this.client.getTransactions(params); - - // Mapped to AptosTransactionByRange internal entity - const aptosEvents = results - .map((result: AptosEventRepository) => { - if (result.events && result.events[0].guid) { - return { - version: result.version, - guid: result.events?.[0]?.guid!, - sequence_number: result.sequence_number!, - type: result.type, - data: result.data, - events: result.events, - hash: result.hash, - payload: result.payload, - }; - } - }) - .filter((x) => x !== undefined) as AptosTransactionByRange[]; - - return aptosEvents; - } catch (e) { - throw e; - } - } - - public async getTransactionByVersion(version: number): Promise { - try { - const result = await this.client.getTransactionByVersion(version); - return result; - } catch (e) { - throw e; - } - } - - public async getBlockByVersion(version: number): Promise { - try { - const result = await this.client.getBlockByVersion(version); - return result; - } catch (e) { - throw e; - } - } -} - -export type HttpClientOptions = { - chain?: string; - url?: string; - initialDelay?: number; - maxDelay?: number; - retries?: number; - timeout?: number; -}; - -type AptosEventRepository = { - sequence_number?: string; - timestamp?: string; - success?: boolean; - version?: string; - payload?: any; - events?: any[]; - sender?: string; - hash: string; - data?: any; - type: string; -}; - -type EventsByEventHandle = { - version?: string; - guid: any; - sequence_number: string; - type: string; - data: any; -}; From 4e212e24eddfe88c82c4cdff3e1eddcdc4166cec Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 18:23:52 -0300 Subject: [PATCH 26/30] Improve test --- .../src/domain/entities/aptos.ts | 13 ++----- blockchain-watcher/src/domain/repositories.ts | 6 ++-- .../aptos/AptosJsonRPCBlockRepository.ts | 7 ++-- .../actions/aptos/GetAptosSequences.test.ts | 36 +------------------ 4 files changed, 9 insertions(+), 53 deletions(-) diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 3ae8c2534..5d8e9bdf4 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -31,17 +31,8 @@ export type AptosTransaction = { hash: string; type?: string; to: string; -}; - -export type AptosTransactionByRange = { - hash?: string; - events?: any; - timestamp?: string; - version?: string; - payload?: any; - data: any; - sequence_number: string; - type: string; + data?: any; + sequence_number?: string; }; export type AptosTransactionByVersion = { diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index ff14596d1..20cc29e65 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -19,7 +19,7 @@ import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; import { SuiTransactionBlockReceipt } from "./entities/sui"; import { TransactionFilter } from "./actions/aptos/PollAptos"; -import { AptosEvent, AptosTransaction, AptosTransactionByRange } from "./entities/aptos"; +import { AptosEvent, AptosTransaction } from "./entities/aptos"; export interface EvmBlockRepository { getBlockHeight(chain: string, finality: string): Promise; @@ -72,13 +72,13 @@ export interface SuiRepository { export interface AptosRepository { getTransactions( range: { from?: number | undefined; limit?: number | undefined } | undefined - ): Promise; + ): Promise; getEventsByEventHandle( range: { from?: number | undefined; limit?: number | undefined } | undefined, filter: TransactionFilter ): Promise; getTransactionsByVersion( - events: AptosEvent[] | AptosTransactionByRange[], + events: AptosEvent[] | AptosTransaction[], filter: TransactionFilter ): Promise; } diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 70aadc3a9..6e95a07b2 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -5,7 +5,6 @@ import { ProviderPool } from "@xlabs/rpc-pool"; import winston from "winston"; import { AptosTransactionByVersion, - AptosTransactionByRange, AptosBlockByVersion, AptosTransaction, AptosEvent, @@ -44,7 +43,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { } async getTransactionsByVersion( - events: AptosEvent[] | AptosTransactionByRange[], + events: AptosEvent[] | AptosTransaction[], filter: TransactionFilter ): Promise { try { @@ -81,9 +80,9 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { } } - async getTransactions(range: Range): Promise { + async getTransactions(range: Range): Promise { try { - let results: AptosTransactionByRange[] = []; + let results: AptosTransaction[] = []; const endpoint = `/transactions?start=${range.from}&limit=${range.limit}`; results = await this.pool.get().get({ endpoint }); diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts index 739b655a0..c4d9f875e 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts @@ -274,44 +274,10 @@ const givenAptosBlockRepository = () => { }, ]; - const aptosTxs = [ - { - guid: { creation_number: "3", account_address: "0x1" }, - type: "block_metadata_transaction", - events: [ - { - guid: { creation_number: "3", account_address: "0x1" }, - sequence_number: "156752466", - type: "0x1::block::NewBlockEvent", - data: { - epoch: "6236", - failed_proposer_indices: [], - hash: "0x245ec3db071a4e014e26f7a427cc5730c354dd621350b798770c9abef0fd6170", - height: "156752466", - previous_block_votes_bitvec: "0xffaa0150beef04c2e533d9fffdff373f", - proposer: "0x15d241369552ece871a16d865a4e9b96f5e6e6f8c7db5478e89af17845969c02", - round: "27858", - time_microseconds: "1710333535940198", - }, - }, - ], - sequence_number: "156752466", - hash: "0x7773df42233d4caad9206faf23487b19b382cdb21e28600b2df3a810eba0b968", - data: { - consistency_level: 1, - nonce: "123123", - payload: {}, - sender: "0x15d241369552ece871a16d865a4e9b96f5e6e6f8c7db5478e89af17845969c02", - sequence: "12312", - timestamp: "123123123", - }, - }, - ]; - aptosRepo = { getEventsByEventHandle: () => Promise.resolve(events), getTransactionsByVersion: () => Promise.resolve(txs), - getTransactions: () => Promise.resolve(aptosTxs), + getTransactions: () => Promise.resolve([]), }; getSequenceNumberSpy = jest.spyOn(aptosRepo, "getEventsByEventHandle"); From c9ff0d5a41151fd751376d496be187a83a4afa23 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 18:57:02 -0300 Subject: [PATCH 27/30] Improve errors message --- blockchain-watcher/src/domain/repositories.ts | 2 +- .../repositories/aptos/AptosJsonRPCBlockRepository.ts | 11 +++++++---- .../aptos/RateLimitedAptosJsonRPCBlockRepository.ts | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 20cc29e65..f4e27abfa 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -78,7 +78,7 @@ export interface AptosRepository { filter: TransactionFilter ): Promise; getTransactionsByVersion( - events: AptosEvent[] | AptosTransaction[], + records: AptosEvent[] | AptosTransaction[], filter: TransactionFilter ): Promise; } diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index 6e95a07b2..d0f066f3d 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -37,18 +37,21 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { return results; } catch (e) { - this.handleError(e, "getEventsByEventHandle"); + this.handleError( + `Range params: ${JSON.stringify(range)}, error: ${e}`, + "getEventsByEventHandle" + ); throw e; } } async getTransactionsByVersion( - events: AptosEvent[] | AptosTransaction[], + records: AptosEvent[] | AptosTransaction[], filter: TransactionFilter ): Promise { try { const transactions = await Promise.all( - events.map(async (event) => { + records.map(async (event) => { const txEndpoint = `/transactions/by_version/${Number(event.version)}`; const blockEndpoint = `/blocks/by_version/${Number(event.version)}`; @@ -89,7 +92,7 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { return results; } catch (e) { - this.handleError(e, "getTransactions"); + this.handleError(`Range params: ${JSON.stringify(range)}, error: ${e}`, "getTransactions"); throw e; } } diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts index b3849b47e..58213bc06 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository.ts @@ -26,9 +26,9 @@ export class RateLimitedAptosJsonRPCBlockRepository } getTransactionsByVersion( - events: AptosEvent[], + records: AptosEvent[], filter: TransactionFilter ): Promise { - return this.breaker.fn(() => this.delegate.getTransactionsByVersion(events, filter)).execute(); + return this.breaker.fn(() => this.delegate.getTransactionsByVersion(records, filter)).execute(); } } From 31e0fce28556683b11bdddee094ac926f3656bad Mon Sep 17 00:00:00 2001 From: julian merlo Date: Wed, 13 Mar 2024 21:21:13 -0300 Subject: [PATCH 28/30] Improve transaction domain and test --- .../actions/aptos/GetAptosTransactions.ts | 56 +++++++-------- .../aptos/GetAptosTransactionsByEvents.ts | 48 ++++++------- .../src/domain/actions/aptos/PollAptos.ts | 2 +- .../src/domain/entities/aptos.ts | 4 -- .../aptos/aptosLogMessagePublishedMapper.ts | 9 ++- .../aptosRedeemedTransactionFoundMapper.ts | 21 +++--- .../aptos/AptosJsonRPCBlockRepository.ts | 6 +- .../rpc/http/InstrumentedHttpProvider.ts | 68 +++++++++---------- .../aptos/GetAptosTransactions.test.ts | 4 +- ...s => GetAptosTransactionsByEvents.test.ts} | 4 +- .../aptos/HandleAptosTransactions.test.ts | 3 - .../aptosLogMessagePublishedMapper.test.ts | 29 ++++++-- ...ptosRedeemedTransactionFoundMapper.test.ts | 15 ++-- 13 files changed, 130 insertions(+), 139 deletions(-) rename blockchain-watcher/test/domain/actions/aptos/{GetAptosSequences.test.ts => GetAptosTransactionsByEvents.test.ts} (98%) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 058bd58d7..331a81ee0 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -22,49 +22,43 @@ export class GetAptosTransactions { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - let { batchSize, totalBatchLimit, limitBatch } = this.createBatch(range); + const from = this.lastFrom ? Number(this.lastFrom) : range?.from; - while (limitBatch <= totalBatchLimit) { - const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; + const transactions = await this.repo.getTransactions({ + from: from, + limit: range?.limit, + }); - const transactions = await this.repo.getTransactions({ - from: fromBatch, - limit: batchSize, - }); - - // Only process transactions to the contract address configured - const transactionsByAddressConfigured = transactions.filter((transaction) => - opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) - ); - - // Update lastFrom with the new lastFrom - this.lastFrom = BigInt(transactions[transactions.length - 1].version!); + // Only process transactions to the contract address configured + const transactionsByAddressConfigured = transactions.filter((transaction) => + opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) + ); - if (opts.previousFrom == this.lastFrom) { - return []; - } + // Update lastFrom with the new lastFrom + this.lastFrom = BigInt(transactions[transactions.length - 1].version!); - // Update previousFrom with opts lastFrom - this.previousFrom = opts.lastFrom; + if (opts.previousFrom == this.lastFrom) { + return []; + } - if (transactionsByAddressConfigured.length > 0) { - const transactions = await this.repo.getTransactionsByVersion( - transactionsByAddressConfigured, - opts.filter - ); + // Update previousFrom with opts lastFrom + this.previousFrom = opts.lastFrom; - transactions.forEach((tx) => { - populatedTransactions.push(tx); - }); - } + if (transactionsByAddressConfigured.length > 0) { + const transactions = await this.repo.getTransactionsByVersion( + transactionsByAddressConfigured, + opts.filter + ); - limitBatch += batchSize; + transactions.forEach((tx) => { + populatedTransactions.push(tx); + }); } return populatedTransactions; } - getBlockRange( + getRange( cfgBlockBarchSize: number, cfgFrom: bigint | undefined, savedpreviousFrom: bigint | undefined, diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts index 4e505d9e1..bab898764 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts @@ -22,37 +22,31 @@ export class GetAptosTransactionsByEvents { `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - let { batchSize, totalBatchLimit, limitBatch } = this.createBatch(opts); - - while (limitBatch <= totalBatchLimit) { - const fromBatch = this.lastFrom ? Number(this.lastFrom) : range?.from; - - const events = await this.repo.getEventsByEventHandle( - { - from: fromBatch, - limit: batchSize, - }, - opts.filter - ); + const from = this.lastFrom ? Number(this.lastFrom) : range?.from; + + const events = await this.repo.getEventsByEventHandle( + { + from: from, + limit: range?.limit, + }, + opts.filter + ); - // Update lastFrom with the new lastFrom - this.lastFrom = BigInt(events[events.length - 1].sequence_number); + // Update lastFrom with the new lastFrom + this.lastFrom = BigInt(events[events.length - 1].sequence_number); - if (opts.previousFrom == this.lastFrom) { - return []; - } - - // Update previousFrom with opts lastFrom - this.previousFrom = opts.lastFrom; + if (opts.previousFrom == this.lastFrom) { + return []; + } - const transactions = await this.repo.getTransactionsByVersion(events, opts.filter); + // Update previousFrom with opts lastFrom + this.previousFrom = opts.lastFrom; - transactions.forEach((tx) => { - populatedTransactions.push(tx); - }); + const transactions = await this.repo.getTransactionsByVersion(events, opts.filter); - limitBatch += batchSize; - } + transactions.forEach((tx) => { + populatedTransactions.push(tx); + }); this.logger.info( `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][from: ${range?.from}]` @@ -60,7 +54,7 @@ export class GetAptosTransactionsByEvents { return populatedTransactions; } - getBlockRange( + getRange( cfgBlockBarchSize: number, cfgFrom: bigint | undefined, savedPreviousSequence: bigint | undefined, diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index d04d5a32a..ed575f10e 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -48,7 +48,7 @@ export class PollAptos extends RunPollingJob { } protected async get(): Promise { - const range = this.getAptos.getBlockRange( + const range = this.getAptos.getRange( this.cfg.getBlockBatchSize(), this.cfg.from, this.previousFrom, diff --git a/blockchain-watcher/src/domain/entities/aptos.ts b/blockchain-watcher/src/domain/entities/aptos.ts index 5d8e9bdf4..174d90f4c 100644 --- a/blockchain-watcher/src/domain/entities/aptos.ts +++ b/blockchain-watcher/src/domain/entities/aptos.ts @@ -18,20 +18,16 @@ export type AptosEvent = Omit & { }; export type AptosTransaction = { - consistencyLevel?: number; blockHeight: bigint; timestamp?: number; blockTime?: number; version: string; payload?: any; - address: string; status?: boolean; events: any; nonce?: number; hash: string; type?: string; - to: string; - data?: any; sequence_number?: string; }; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts index e0a620805..9855bf4e7 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.ts @@ -2,6 +2,7 @@ import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; import { AptosTransaction } from "../../../domain/entities/aptos"; import winston from "winston"; +const REDEEM_EVENT_TAIL = "::state::WormholeMessage"; const CHAIN_ID_APTOS = 22; let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMapper" }); @@ -9,16 +10,18 @@ let logger: winston.Logger = winston.child({ module: "aptosLogMessagePublishedMa export const aptosLogMessagePublishedMapper = ( transaction: AptosTransaction ): LogFoundEvent | undefined => { - const wormholeEvent = transaction.events.find((tx: any) => tx.type === transaction.type); + const wormholeEvent = transaction.events.find((tx: any) => tx.type.includes(REDEEM_EVENT_TAIL)); const wormholeData = wormholeEvent.data; + const address = transaction.payload.function.split("::")[0]; + logger.info( `[aptos] Source event info: [tx: ${transaction.hash}][emitterChain: ${CHAIN_ID_APTOS}][sender: ${wormholeData.sender}}][sequence: ${wormholeData.sequence}]` ); return { name: "log-message-published", - address: transaction.address, + address: address, chainId: CHAIN_ID_APTOS, txHash: transaction.hash, blockHeight: transaction.blockHeight, @@ -28,7 +31,7 @@ export const aptosLogMessagePublishedMapper = ( sequence: Number(wormholeData.sequence), payload: wormholeData.payload, nonce: Number(wormholeData.nonce), - consistencyLevel: transaction.consistencyLevel!, + consistencyLevel: wormholeData.consistencyLevel, }, }; }; diff --git a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts index bc490b537..5b7d66ffa 100644 --- a/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.ts @@ -10,33 +10,36 @@ let logger: winston.Logger = winston.child({ module: "aptosRedeemedTransactionFo const APTOS_CHAIN = "aptos"; export const aptosRedeemedTransactionFoundMapper = ( - tx: AptosTransaction + transaction: AptosTransaction ): TransactionFoundEvent | undefined => { - const protocol = findProtocol(APTOS_CHAIN, tx.to, tx.type!, tx.hash); + const address = transaction.payload.function.split("::")[0]; + const type = transaction.payload.function; - const vaaBuffer = Buffer.from(tx.payload?.arguments[0]?.substring(2), "hex"); + const protocol = findProtocol(APTOS_CHAIN, address, type, transaction.hash); + + const vaaBuffer = Buffer.from(transaction.payload?.arguments[0]?.substring(2), "hex"); const vaa = parseVaa(vaaBuffer); const emitterAddress = vaa.emitterAddress.toString("hex"); if (protocol && protocol.type && protocol.method) { logger.info( - `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${tx.hash}][VAA: ${vaa.emitterChain}/${emitterAddress}/${vaa.sequence}]` + `[${APTOS_CHAIN}] Redeemed transaction info: [hash: ${transaction.hash}][VAA: ${vaa.emitterChain}/${emitterAddress}/${vaa.sequence}]` ); return { name: "transfer-redeemed", - address: tx.to, - blockHeight: tx.blockHeight, + address: address, + blockHeight: transaction.blockHeight, blockTime: vaa.timestamp, chainId: CHAIN_ID_APTOS, - txHash: tx.hash, + txHash: transaction.hash, attributes: { - from: tx.address, + from: address, emitterChain: vaa.emitterChain, emitterAddress: emitterAddress, sequence: Number(vaa.sequence), - status: tx.status === true ? TxStatus.Completed : TxStatus.Failed, + status: transaction.status === true ? TxStatus.Completed : TxStatus.Failed, protocol: protocol.method, }, }; diff --git a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts index d0f066f3d..ed45dfc6a 100644 --- a/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/aptos/AptosJsonRPCBlockRepository.ts @@ -62,16 +62,12 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { blockResult = await this.pool.get().get({ endpoint: blockEndpoint }); return { - consistencyLevel: event?.data?.consistency_level, blockHeight: BigInt(blockResult.block_height!), version: txResult.version!, - address: event.events ? event.events[0].guid.account_address : filter.address, status: txResult.success, events: txResult.events, hash: txResult.hash!, - type: filter.type, payload: txResult.payload, - to: filter.address, }; }) ); @@ -98,6 +94,6 @@ export class AptosJsonRPCBlockRepository implements AptosRepository { } private handleError(e: any, method: string) { - this.logger.error(`[aptos] Error calling ${method}: ${e.message}`); + this.logger.error(`[aptos] Error calling ${method}: ${e.message ?? e}`); } } diff --git a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts index 277c0ff8e..47db4b50d 100644 --- a/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/InstrumentedHttpProvider.ts @@ -37,23 +37,39 @@ export class InstrumentedHttpProvider { } public async post(body: any, opts?: HttpClientOptions): Promise { - return this.executePost("POST", body, opts); + return this.execute("POST", body, undefined, opts); } - private async executePost(method: string, body?: any, opts?: HttpClientOptions): Promise { + public async get(params: any, opts?: HttpClientOptions): Promise { + return this.execute("GET", undefined, params, opts); + } + + private async execute( + method: string, + body?: any, + params?: any, + opts?: HttpClientOptions + ): Promise { let response; try { - response = await this.health.fetch(this.url, { - method: method, - body: JSON.stringify(body), + const requestOpts: RequestOpts = { + method, signal: AbortSignal.timeout(opts?.timeout ?? this.timeout), headers: { "Content-Type": "application/json", }, - }); + }; + + if (method === "POST") { + requestOpts.body = JSON.stringify(body); + } + + const url = method === "POST" ? this.url : `${this.url}${params.endpoint}`; + + response = await this.health.fetch(url, requestOpts); } catch (e: AxiosError | any) { this.logger.error( - `[${this.chain}][${body?.method ?? body[0]?.method}] Got error from ${this.url} rpc. ${ + `[${this.chain}][${body?.method ?? method}] Got error from ${this.url} rpc. ${ e?.message ?? `${e}` }` ); @@ -72,35 +88,6 @@ export class InstrumentedHttpProvider { return response.json() as T; } - - public async get(params: any, opts?: HttpClientOptions): Promise { - return this.executeGet("GET", params, opts); - } - - private async executeGet(method: string, params?: any, opts?: HttpClientOptions): Promise { - let response; - try { - response = await this.health.fetch(`${this.url}${params.endpoint}`, { - method: method, - signal: AbortSignal.timeout(opts?.timeout ?? this.timeout), - headers: { - "Content-Type": "application/json", - }, - }); - } catch (e: AxiosError | any) { - // Connection / timeout error: - if (e instanceof AxiosError) { - throw new HttpClientError(e.message ?? e.code, { status: e?.status ?? 0 }, e); - } - - throw new HttpClientError(e.message ?? e.code, undefined, e); - } - - if (!(response.status > 200) && !(response.status < 300)) { - throw new HttpClientError(undefined, response, response.json()); - } - return response.json() as T; - } } export type HttpClientOptions = { @@ -111,3 +98,12 @@ export type HttpClientOptions = { retries?: number; timeout?: number; }; + +type RequestOpts = { + method: string; + signal: AbortSignal; + headers: { + "Content-Type": string; + }; + body?: string; +}; diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts index e5a1e3f8d..3b3de2ba6 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactions.test.ts @@ -120,7 +120,7 @@ describe("GetAptosTransactions", () => { }; givenAptosBlockRepository(tx); - givenMetadataRepository(); + givenMetadataRepository({ lastFrom: 423525334n }); givenStatsRepository(); givenPollAptosTx(cfg); @@ -130,7 +130,7 @@ describe("GetAptosTransactions", () => { // Then await thenWaitForAssertion( () => expect(getTransactionsSpy).toHaveReturnedTimes(1), - () => expect(getTransactionsSpy).toBeCalledWith({ from: undefined, limit: 100 }) + () => expect(getTransactionsSpy).toBeCalledWith({ from: 423525334, limit: 100 }) ); }); diff --git a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactionsByEvents.test.ts similarity index 98% rename from blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts rename to blockchain-watcher/test/domain/actions/aptos/GetAptosTransactionsByEvents.test.ts index c4d9f875e..0e2180200 100644 --- a/blockchain-watcher/test/domain/actions/aptos/GetAptosSequences.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/GetAptosTransactionsByEvents.test.ts @@ -59,7 +59,7 @@ describe("GetAptosTransactionsByEvents", () => { it("should be not generate range (from and limit) and search the latest from plus from batch size cfg", async () => { // Given givenAptosBlockRepository(); - givenMetadataRepository(); + givenMetadataRepository({ previousFrom: undefined, lastFrom: 146040n }); givenStatsRepository(); givenPollAptosTx(cfg); @@ -71,7 +71,7 @@ describe("GetAptosTransactionsByEvents", () => { () => expect(getSequenceNumberSpy).toHaveReturnedTimes(1), () => expect(getSequenceNumberSpy).toBeCalledWith( - { from: undefined, limit: 100 }, + { from: 146040, limit: 100 }, { address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", event: diff --git a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts index 9f47d49ac..b598d7d98 100644 --- a/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts +++ b/blockchain-watcher/test/domain/actions/aptos/HandleAptosTransactions.test.ts @@ -100,16 +100,13 @@ const givenStatsRepository = () => { txs = [ { - consistencyLevel: 0, blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, version: "482649547", payload: "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", - address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", status: true, - to: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", events: [ { guid: { diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts index 902f0028e..6da88ff31 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosLogMessagePublishedMapper.test.ts @@ -15,7 +15,7 @@ describe("aptosLogMessagePublishedMapper", () => { "0xb2fa774485ce02c5786475dd2d689c3e3c2d0df0c5e09a1c8d1d0e249d96d76e" ); expect(result.address).toBe( - "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" + "0xb1421c3d524a353411aa4e3cce0f0ce7f404a12da91a2889e1bc3cea6ffb17da" ); expect(result.attributes.consistencyLevel).toBe(0); expect(result.attributes.nonce).toBe(76704); @@ -29,16 +29,32 @@ describe("aptosLogMessagePublishedMapper", () => { }); const txs: AptosTransaction = { - consistencyLevel: 0, blockHeight: 153517771n, timestamp: 170963869344, blockTime: 170963869344, version: "482649547", - payload: - "0x01000000000000000000000000000000000000000000000000000000000097d3650000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000500000000000000000000000081c1980abe8971e14865a629dd75b07621db1ae100050000000000000000000000000000000000000000000000000000000000001fe0", - address: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", + payload: { + function: + "0xb1421c3d524a353411aa4e3cce0f0ce7f404a12da91a2889e1bc3cea6ffb17da::cancel_and_place::cancel_and_place", + type_arguments: [ + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::WETH", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC", + ], + arguments: [ + "0x01000000030d010f845d9923156821372114d23c633a86d8651cf79524764555dc9711631de5a4623f6cec4127b1e6872532bd24c148e326d324c09e413dc0ca222ec3dbc56abd00035fe16475cbf0d4f1a07f76dffd9690dc56fdeee3dce8f15f0dc4c71e32964ef72cb8b3b41c042912622dfb0de69e92dc77387f51659fdf85d7783b9cc8c0662d0104f982da1feb8b0b6a33ab69e395bda21dc1125fa1bae8386174c989f921f2bd71794ebaca92fed4c92be13cc031af8c27678b65ae2a2bf891b689ab15050bf62b000696f6ec7f68b7a0bf659f541a02e98a8c6e054b9ed7c3b654d97e3db678efa0f500465d833d307e23da40cbbc52e42457bb1d52e36a5b44b557cc84a970ae4ee40107097002812bea42577b5ba3c570a4b7f580a89144d2941aa699787eeaebaffb027d7ba89fd01ce2d635356ba63a7b25fd880b6e033a69717641ceabeb0fd2df2a0008e0faeafb1e8b2bd0dcaf807e7185ec2c425725e53cf933f713ac3e1461a712400b004d913c07d97d5fa2e7aed8a42cf3f3a7bca1fa74978653dcb235a5ba8eb60109610dd1508c204ff306584dba43a8bd86bc7c9fabefae661f367d4a01fd82bdfb5e5ed9a72cc4b0a5d12f6a47de5e6b66c3498afbb3b242012355ae5a1658a0ab010a7bb4707ee2af704ac0de725bc7c3ba9f776a595244d21203a05211cdfbfc550e5281a16d3f9f5d691a4b26a11af22a682b8f317c2cda55bde9272d2d68f9ed2e010c85d6087988bed609680b2047820bf14fca38001d2702607fe5f50a05a2b9533f2460be246d1c249e90aac396b06aaecb90304b9c4748d3ff6280a48338f7f4fc000df65098fbae3333d0dbaef9b10c50f2ec0d395e899d939c1a91363fa570d711e955d20eec3c7fba59f8198ad2ef5746a9a21ff69441c2b460ad4e8a55f3fb21450010493b06f48838dcf965e08bd266473d4275c5b3b73e70ca03fbeb198f5d610e955c6d11f16b9e592e0f85fe6d1f7f82118e6933541bb90dd20ad41c884a0308a4011123515dc5e8222adec3c31130bd245e43a4a7b435bb531027f7632fcd23607a92592d0769557558686f5cc8ba606096ea181829bc0d54e36a4d249a59f74034e50012db6bf0b9aad11fbb5959128a1103dc5d948d6ede23359626700d1b2c6dff9be1137888f72bd0b79fd7c36e511c05c1475a837954948e313b981e6c56123d1d270165f0be060000410f0015ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5000000000001b50b00010000000000000000000000000000000000000000000000000000000004d13720000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000234226de67ee40839dff5760c2725bd2d5d7817fea499126c533e69f5702c5a7d00160000000000000000000000000000000000000000000000000000000000000000", + ["42645072269700382266349876", "42644869357063623703648693"], + ["42645090718696431883245683", "42644906249285249691670581"], + ["2517", "10041"], + ["383120", "383216"], + ["2539", "10008"], + ["382928", "382832"], + "0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78f", + 3, + 0, + ], + type: "entry_function_payload", + }, status: true, - to: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625", events: [ { guid: { @@ -82,6 +98,7 @@ const txs: AptosTransaction = { sender: "1", sequence: "146094", timestamp: "1709638693", + consistencyLevel: 0, }, }, { diff --git a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts index d5525f164..522e51ec3 100644 --- a/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts +++ b/blockchain-watcher/test/infrastructure/mappers/aptos/aptosRedeemedTransactionFoundMapper.test.ts @@ -18,13 +18,13 @@ describe("aptosRedeemedTransactionFoundMapper", () => { "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f" ); expect(result.attributes.from).toBe( - "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" + "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f" ); - expect(result.attributes.emitterChain).toBe(5); + expect(result.attributes.emitterChain).toBe(21); expect(result.attributes.emitterAddress).toBe( - "0000000000000000000000005a58505a96d1dbf8df91cb21b54419fc36e93fde" + "ccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5" ); - expect(result.attributes.sequence).toBe(394768); + expect(result.attributes.sequence).toBe(111883); expect(result.attributes.status).toBe("completed"); expect(result.attributes.protocol).toBe("Token Bridge"); } @@ -33,7 +33,6 @@ describe("aptosRedeemedTransactionFoundMapper", () => { it("should not be able to map log to aptosRedeemedTransactionFoundMapper", async () => { // Given const tx: AptosTransaction = { - consistencyLevel: 15, blockHeight: 154363203n, timestamp: 1709821894, blockTime: 1709821894, @@ -59,9 +58,7 @@ describe("aptosRedeemedTransactionFoundMapper", () => { ], type: "entry_function_payload", }, - address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", status: true, - to: "0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea", events: [], nonce: 302448640, hash: "0xd297f46372ed734fd8f3b595a898f42ab328a4e3cc9ce0f810c6c66e64873e64", @@ -76,7 +73,6 @@ describe("aptosRedeemedTransactionFoundMapper", () => { }); const tx: AptosTransaction = { - consistencyLevel: 15, blockHeight: 154363203n, timestamp: 1709821894, blockTime: 1709821894, @@ -90,9 +86,8 @@ const tx: AptosTransaction = { ], type: "entry_function_payload", }, - address: "0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f", + status: true, - to: "0x5e156f1207d0ebfa19a9eeff00d62a282278fb8719f4fab3a586a0a2c0fffbea", events: [ { guid: { From f9a0f532de3a37b81e75e4b2f00001d5c7594bb0 Mon Sep 17 00:00:00 2001 From: julian merlo Date: Thu, 14 Mar 2024 08:54:04 -0300 Subject: [PATCH 29/30] Remove block name in variables --- .../actions/aptos/GetAptosTransactions.ts | 24 +++++------------- .../aptos/GetAptosTransactionsByEvents.ts | 25 ++++--------------- .../src/domain/actions/aptos/PollAptos.ts | 14 +++++------ .../workers/source-events-1.yaml | 4 +-- .../workers/target-events-2.yaml | 6 ++--- 5 files changed, 23 insertions(+), 50 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index 331a81ee0..d340c6913 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -19,7 +19,7 @@ export class GetAptosTransactions { let populatedTransactions: AptosTransaction[] = []; this.logger.info( - `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` + `[aptos][exec] Processing range [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); const from = this.lastFrom ? Number(this.lastFrom) : range?.from; @@ -59,7 +59,7 @@ export class GetAptosTransactions { } getRange( - cfgBlockBarchSize: number, + cfgLimitBatchSize: number, cfgFrom: bigint | undefined, savedpreviousFrom: bigint | undefined, savedlastFrom: bigint | undefined @@ -68,16 +68,16 @@ export class GetAptosTransactions { if (cfgFrom) { return { from: Number(cfgFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } if (savedpreviousFrom && savedlastFrom) { - // If process [equal or different blocks], return the same lastFrom and limit equal the from batch size + // If process [equal or different from], return the same lastFrom and limit equal the from batch size if (savedpreviousFrom === savedlastFrom || savedpreviousFrom !== savedlastFrom) { return { from: Number(savedlastFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } } @@ -87,7 +87,7 @@ export class GetAptosTransactions { if (!cfgFrom || BigInt(cfgFrom) < savedlastFrom) { return { from: Number(savedlastFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } } @@ -99,16 +99,4 @@ export class GetAptosTransactions { lastFrom: this.lastFrom, }; } - - private createBatch(range: Range | undefined) { - const batchSize = 100; - const totalBatchLimit = range?.limit ?? batchSize; - let limitBatch = 100; - - return { - batchSize, - totalBatchLimit, - limitBatch, - }; - } } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts index bab898764..d12f8fc2c 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts @@ -19,7 +19,7 @@ export class GetAptosTransactionsByEvents { let populatedTransactions: AptosTransaction[] = []; this.logger.info( - `[aptos][exec] Processing blocks [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` + `[aptos][exec] Processing range [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); const from = this.lastFrom ? Number(this.lastFrom) : range?.from; @@ -55,7 +55,7 @@ export class GetAptosTransactionsByEvents { } getRange( - cfgBlockBarchSize: number, + cfgLimitBatchSize: number, cfgFrom: bigint | undefined, savedPreviousSequence: bigint | undefined, savedlastFrom: bigint | undefined @@ -64,7 +64,7 @@ export class GetAptosTransactionsByEvents { if (cfgFrom) { return { from: Number(cfgFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } @@ -73,7 +73,7 @@ export class GetAptosTransactionsByEvents { if (savedPreviousSequence === savedlastFrom) { return { from: Number(savedlastFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } else { // If process [different sequences], return the difference between the lastFrom and the previousFrom plus 1 @@ -89,7 +89,7 @@ export class GetAptosTransactionsByEvents { if (!cfgFrom || BigInt(cfgFrom) < savedlastFrom) { return { from: Number(savedlastFrom), - limit: cfgBlockBarchSize, + limit: cfgLimitBatchSize, }; } } @@ -101,19 +101,4 @@ export class GetAptosTransactionsByEvents { lastFrom: this.lastFrom, }; } - - private createBatch(opts: GetAptosOpts) { - const batchSize = 100; - const totalBatchLimit = - opts.previousFrom && opts.lastFrom - ? Number(opts.lastFrom) - Number(opts.previousFrom) + 1 - : batchSize; - let limitBatch = totalBatchLimit < batchSize ? 1 : batchSize; - - return { - batchSize, - totalBatchLimit, - limitBatch, - }; - } } diff --git a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts index ed575f10e..4ad752fae 100644 --- a/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts +++ b/blockchain-watcher/src/domain/actions/aptos/PollAptos.ts @@ -6,8 +6,8 @@ import winston, { Logger } from "winston"; import { RunPollingJob } from "../RunPollingJob"; export class PollAptos extends RunPollingJob { - protected readonly logger: Logger; private readonly getAptos: GetAptosTransactionsByEvents; + protected readonly logger: Logger; private previousFrom?: bigint; private lastFrom?: bigint; @@ -49,7 +49,7 @@ export class PollAptos extends RunPollingJob { protected async get(): Promise { const range = this.getAptos.getRange( - this.cfg.getBlockBatchSize(), + this.cfg.getLimitBatchSize(), this.cfg.from, this.previousFrom, this.lastFrom @@ -62,12 +62,12 @@ export class PollAptos extends RunPollingJob { lastFrom: this.lastFrom, }); - this.updateBlockRange(); + this.updateRange(); return records; } - private updateBlockRange(): void { + private updateRange(): void { // Update the previousFrom and lastFrom based on the executed range const updatedRange = this.getAptos.getUpdatedRange(); if (updatedRange) { @@ -106,8 +106,8 @@ export class PollAptos extends RunPollingJob { export class PollAptosTransactionsConfig { constructor(private readonly props: PollAptosTransactionsConfigProps) {} - public getBlockBatchSize() { - return this.props.blockBatchSize ?? 100; + public getLimitBatchSize() { + return this.props.limitBatchSize ?? 100; } public getCommitment() { @@ -140,7 +140,7 @@ export class PollAptosTransactionsConfig { } export interface PollAptosTransactionsConfigProps { - blockBatchSize?: number; + limitBatchSize?: number; from?: bigint; limit?: bigint; environment: string; diff --git a/deploy/blockchain-watcher/workers/source-events-1.yaml b/deploy/blockchain-watcher/workers/source-events-1.yaml index 3ea37690e..b5fdafb93 100644 --- a/deploy/blockchain-watcher/workers/source-events-1.yaml +++ b/deploy/blockchain-watcher/workers/source-events-1.yaml @@ -312,7 +312,7 @@ data: "source": { "action": "PollAptos", "config": { - "blockBatchSize": 100, + "limitBatchSize": 100, "commitment": "finalized", "interval": 15000, "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], @@ -562,7 +562,7 @@ data: "source": { "action": "PollAptos", "config": { - "blockBatchSize": 100, + "limitBatchSize": 100, "commitment": "finalized", "interval": 15000, "addresses": ["0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625"], diff --git a/deploy/blockchain-watcher/workers/target-events-2.yaml b/deploy/blockchain-watcher/workers/target-events-2.yaml index e0058c76d..931bd5022 100644 --- a/deploy/blockchain-watcher/workers/target-events-2.yaml +++ b/deploy/blockchain-watcher/workers/target-events-2.yaml @@ -43,7 +43,7 @@ data: "action": "PollAptos", "records": "GetAptosTransactions", "config": { - "blockBatchSize": 1000, + "limitBatchSize": 100, "commitment": "finalized", "interval": 5000, "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], @@ -76,9 +76,9 @@ data: "action": "PollAptos", "records": "GetAptosTransactions", "config": { - "blockBatchSize": 1000, + "limitBatchSize": 100, "commitment": "finalized", - "interval": 5000, + "interval": 3000, "addresses": ["0x576410486a2da45eee6c949c995670112ddf2fbeedab20350d506328eefc9d4f"], "chain": "aptos", "chainId": 22, From 5fc18df32e8782cb3c76d6743cfbf45c9515cdcc Mon Sep 17 00:00:00 2001 From: julian merlo Date: Thu, 14 Mar 2024 18:57:17 -0300 Subject: [PATCH 30/30] Resolved comment in PR --- .../actions/aptos/GetAptosTransactions.ts | 20 ++++++++++--------- .../aptos/GetAptosTransactionsByEvents.ts | 19 +++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts index d340c6913..43a6d5643 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactions.ts @@ -22,10 +22,8 @@ export class GetAptosTransactions { `[aptos][exec] Processing range [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const from = this.lastFrom ? Number(this.lastFrom) : range?.from; - const transactions = await this.repo.getTransactions({ - from: from, + from: range?.from, limit: range?.limit, }); @@ -34,16 +32,12 @@ export class GetAptosTransactions { opts.filter?.type?.includes(String(transaction.payload?.function).toLowerCase()) ); - // Update lastFrom with the new lastFrom - this.lastFrom = BigInt(transactions[transactions.length - 1].version!); + const newLastFrom = BigInt(transactions[transactions.length - 1].version!); - if (opts.previousFrom == this.lastFrom) { + if (opts.previousFrom == newLastFrom) { return []; } - // Update previousFrom with opts lastFrom - this.previousFrom = opts.lastFrom; - if (transactionsByAddressConfigured.length > 0) { const transactions = await this.repo.getTransactionsByVersion( transactionsByAddressConfigured, @@ -55,6 +49,14 @@ export class GetAptosTransactions { }); } + this.logger.info( + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][from: ${range?.from} - limit: ${range?.limit}]` + ); + + // Update lastFrom and previousFrom with the new lastFrom + this.lastFrom = BigInt(transactions[transactions.length - 1].version!); + this.previousFrom = opts.lastFrom; + return populatedTransactions; } diff --git a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts index d12f8fc2c..4c4eeff7f 100644 --- a/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts +++ b/blockchain-watcher/src/domain/actions/aptos/GetAptosTransactionsByEvents.ts @@ -22,26 +22,20 @@ export class GetAptosTransactionsByEvents { `[aptos][exec] Processing range [previousFrom: ${opts.previousFrom} - lastFrom: ${opts.lastFrom}]` ); - const from = this.lastFrom ? Number(this.lastFrom) : range?.from; - const events = await this.repo.getEventsByEventHandle( { - from: from, + from: range?.from, limit: range?.limit, }, opts.filter ); - // Update lastFrom with the new lastFrom - this.lastFrom = BigInt(events[events.length - 1].sequence_number); + const newLastFrom = BigInt(events[events.length - 1].sequence_number); - if (opts.previousFrom == this.lastFrom) { + if (opts.previousFrom == newLastFrom) { return []; } - // Update previousFrom with opts lastFrom - this.previousFrom = opts.lastFrom; - const transactions = await this.repo.getTransactionsByVersion(events, opts.filter); transactions.forEach((tx) => { @@ -49,8 +43,13 @@ export class GetAptosTransactionsByEvents { }); this.logger.info( - `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][from: ${range?.from}]` + `[aptos][exec] Got ${populatedTransactions?.length} transactions to process for [addresses:${opts.addresses}][from: ${range?.from} - limit: ${range?.limit}]` ); + + // Update lastFrom and previousFrom with opts lastFrom + this.lastFrom = BigInt(events[events.length - 1].sequence_number); + this.previousFrom = opts.lastFrom; + return populatedTransactions; }