diff --git a/blockchain-watcher/.prettierrc.json b/blockchain-watcher/.prettierrc.json deleted file mode 100644 index de753c53..00000000 --- a/blockchain-watcher/.prettierrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "printWidth": 100 -} diff --git a/blockchain-watcher/Dockerfile b/blockchain-watcher/Dockerfile index caa6cb35..bff58cf0 100644 --- a/blockchain-watcher/Dockerfile +++ b/blockchain-watcher/Dockerfile @@ -1,12 +1,4 @@ -# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2 -FROM node:19.6.1-slim@sha256:a1ba21bf0c92931d02a8416f0a54daad66cb36a85d2b73af9d73b044f5f57cfc as builder - -# npm wants to clone random Git repositories - lovely. -# RUN apk add git python make build-base -# RUN apk update && apk add bash -RUN apt-get update && apt-get -y install \ - git python make curl netcat - +FROM node:18.19.0-alpine3.18@sha256:c0a5f02df6e631b75ee3037bd4389ac1f91e591c5c1e30a0007a7d0babcd4cd3 as builder USER 1000 RUN mkdir -p /home/node/app @@ -14,35 +6,20 @@ RUN mkdir -p /home/node/.npm WORKDIR /home/node/app -# Fix git ssh error -RUN git config --global url."https://".insteadOf ssh:// - -# Node -ENV NODE_EXTRA_CA_CERTS=/certs/cert.pem -ENV NODE_OPTIONS=--use-openssl-ca -# npm -RUN if [ -e /certs/cert.pem ]; then npm config set cafile /certs/cert.pem; fi -# git -RUN if [ -e /certs/cert.pem ]; then git config --global http.sslCAInfo /certs/cert.pem; fi - COPY --chown=node:node . . RUN npm ci -RUN npm run build +RUN npm run build:ncc -FROM node:19.6.1-slim@sha256:a1ba21bf0c92931d02a8416f0a54daad66cb36a85d2b73af9d73b044f5f57cfc as runner +FROM node:18.19.0-alpine3.18@sha256:c0a5f02df6e631b75ee3037bd4389ac1f91e591c5c1e30a0007a7d0babcd4cd3 as runner COPY --from=builder /home/node/app/config /home/node/app/config COPY --from=builder /home/node/app/lib /home/node/app/lib WORKDIR /home/node/app - COPY package.json . -COPY package-lock.json . -RUN npm install --omit=dev - -CMD [ "npm", "start" ] +CMD [ "npm", "run", "start:ncc" ] diff --git a/blockchain-watcher/TODO.md b/blockchain-watcher/TODO.md deleted file mode 100644 index e11e4443..00000000 --- a/blockchain-watcher/TODO.md +++ /dev/null @@ -1,18 +0,0 @@ -Each blockchain has three watchers, - -- A websocket watcher for low latency -- A querying watcher -- And a sequence gap watcher - -These three watchers all invoke the same handler callback. - -The handler callback is responsible for: - -- Providing the watchers with the necessary query filter information -- parsing the event into a persistence object -- invoking the persistence manager - -The persistence manager is responsible for: - -- Inserting records into the database in a safe manner, which takes into account that items will be seen multiple times. -- Last write wins should be the approach taken here. diff --git a/blockchain-watcher/config/custom-environment-variables.json b/blockchain-watcher/config/custom-environment-variables.json index bf837995..e81c2ee1 100644 --- a/blockchain-watcher/config/custom-environment-variables.json +++ b/blockchain-watcher/config/custom-environment-variables.json @@ -3,6 +3,10 @@ "port": "PORT", "logLevel": "LOG_LEVEL", "dryRun": "DRY_RUN_ENABLED", + "supportedChains": { + "__name": "SUPPORTED_CHAINS", + "__format": "json" + }, "jobs": { "dir": "JOBS_DIR" }, @@ -10,17 +14,44 @@ "topicArn": "SNS_TOPIC_ARN", "region": "SNS_REGION" }, - "platforms": { + "chains": { "ethereum": { + "network": "ETHEREUM_NETWORK", "rpcs": { "__name": "ETHEREUM_RPCS", "__format": "json" } }, "solana": { + "network": "SOLANA_NETWORK", "rpcs": { "__name": "SOLANA_RPCS", "__format": "json" + }, + "rateLimit": { + "period": "SOLANA_RATE_LIMIT_PERIOD", + "limit": "SOLANA_RATE_LIMIT_LIMIT" + } + }, + "karura": { + "network": "KARURA_NETWORK", + "rpcs": { + "__name": "KARURA_RPCS", + "__format": "json" + } + }, + "fantom": { + "network": "FANTOM_NETWORK", + "rpcs": { + "__name": "FANTOM_RPCS", + "__format": "json" + } + }, + "acala": { + "network": "FANTOM_NETWORK", + "rpcs": { + "__name": "FANTOM_RPCS", + "__format": "json" } } } diff --git a/blockchain-watcher/config/default.json b/blockchain-watcher/config/default.json index 2499b22c..310e695d 100644 --- a/blockchain-watcher/config/default.json +++ b/blockchain-watcher/config/default.json @@ -16,7 +16,7 @@ "jobs": { "dir": "metadata-repo/jobs" }, - "platforms": { + "chains": { "solana": { "name": "solana", "network": "devnet", diff --git a/blockchain-watcher/config/prod-mainnet.json b/blockchain-watcher/config/mainnet.json similarity index 92% rename from blockchain-watcher/config/prod-mainnet.json rename to blockchain-watcher/config/mainnet.json index 907c996e..6313b613 100644 --- a/blockchain-watcher/config/prod-mainnet.json +++ b/blockchain-watcher/config/mainnet.json @@ -1,7 +1,7 @@ { - "platforms": { + "chains": { "solana": { - "network": "mainnet", + "network": "mainnet-beta", "rpcs": ["https://api.mainnet-beta.solana.com"] }, "ethereum": { diff --git a/blockchain-watcher/config/staging-mainnet.json b/blockchain-watcher/config/staging-mainnet.json deleted file mode 100644 index af213855..00000000 --- a/blockchain-watcher/config/staging-mainnet.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "platforms": { - "solana": { - "name": "solana", - "network": "mainnet-beta", - "chainId": 1, - "rpcs": ["https://api.mainnet-beta.solana.com"], - "timeout": 10000 - }, - "ethereum": { - "name": "ethereum", - "network": "mainnet", - "chainId": 2, - "rpcs": ["https://rpc.ankr.com/eth"], - "timeout": 10000 - }, - "avalanche": { - "name": "avalanche", - "network": "mainnet", - "chainId": 6, - "rpcs": ["https://api.avax.network/ext/bc/C/rpc"], - "timeout": 10000 - }, - "fantom": { - "name": "fantom", - "network": "mainnet", - "chainId": 10, - "rpcs": ["https://rpcapi.fantom.network"], - "timeout": 10000 - }, - "karura": { - "name": "karura", - "network": "mainnet", - "chainId": 11, - "rpcs": ["https://eth-rpc-karura.aca-api.network"], - "timeout": 10000 - }, - "acala": { - "name": "acala", - "network": "mainnet", - "chainId": 12, - "rpcs": ["https://eth-rpc-acala.aca-api.network"], - "timeout": 10000 - } - } -} diff --git a/blockchain-watcher/config/staging.json b/blockchain-watcher/config/staging.json deleted file mode 100644 index 0967ef42..00000000 --- a/blockchain-watcher/config/staging.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/blockchain-watcher/config/test.json b/blockchain-watcher/config/test.json deleted file mode 100644 index 0967ef42..00000000 --- a/blockchain-watcher/config/test.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/blockchain-watcher/config/prod-testnet.json b/blockchain-watcher/config/testnet.json similarity index 100% rename from blockchain-watcher/config/prod-testnet.json rename to blockchain-watcher/config/testnet.json diff --git a/blockchain-watcher/jest.config.js b/blockchain-watcher/jest.config.js deleted file mode 100644 index 5f771bc8..00000000 --- a/blockchain-watcher/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - moduleFileExtensions: ["js", "json", "ts"], - setupFiles: ["/src/infrastructure/log.ts"], - roots: ["test", "src"], - testRegex: ".*\\.test\\.ts$", - transform: { - "^.+\\.(t|j)s$": "ts-jest", - }, - collectCoverage: true, - collectCoverageFrom: ["**/*.(t|j)s"], - coveragePathIgnorePatterns: ["node_modules", "test"], - coverageDirectory: "./coverage", - coverageThreshold: { - global: { - lines: 63.9, - }, - }, -}; diff --git a/blockchain-watcher/package-lock.json b/blockchain-watcher/package-lock.json index 23c5e8bd..c340da48 100644 --- a/blockchain-watcher/package-lock.json +++ b/blockchain-watcher/package-lock.json @@ -10,12 +10,11 @@ "license": "ISC", "dependencies": { "@aws-sdk/client-sns": "^3.445.0", - "@certusone/wormhole-sdk": "^0.9.21-beta.0", + "@certusone/wormhole-sdk": "0.10.5", "@types/config": "^3.3.3", "axios": "^1.6.0", "bs58": "^5.0.0", "config": "^3.3.9", - "dotenv": "^16.3.1", "ethers": "^5", "mollitia": "^0.1.0", "prom-client": "^15.0.0", @@ -27,6 +26,7 @@ "@types/koa-router": "^7.4.4", "@types/uuid": "^9.0.6", "@types/yargs": "^17.0.23", + "@vercel/ncc": "^0.38.1", "jest": "^29.7.0", "nock": "^13.3.8", "prettier": "^2.8.7", @@ -1190,9 +1190,9 @@ "dev": true }, "node_modules/@certusone/wormhole-sdk": { - "version": "0.9.21-beta.0", - "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.21-beta.0.tgz", - "integrity": "sha512-44oa/bvVKEtFnIwkg+NdGfVjfyiLdguRBVe4g1EK3Y/Yd6VcPtnETwJU5z5SXJvoV2KnfpnSmK8B+tMKfQ6kbg==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.10.5.tgz", + "integrity": "sha512-wKONuigkakoFx9HplBt2Jh5KPxc7xgtDJVrIb2/SqYWbFrdpiZrMC4H6kTZq2U4+lWtqaCa1aJ1q+3GOTNx2CQ==", "dependencies": { "@certusone/wormhole-sdk-proto-web": "0.0.6", "@certusone/wormhole-sdk-wasm": "^0.0.1", @@ -1203,7 +1203,7 @@ "@solana/web3.js": "^1.66.2", "@terra-money/terra.js": "3.1.9", "@xpla/xpla.js": "^0.2.1", - "algosdk": "^1.15.0", + "algosdk": "^2.4.0", "aptos": "1.5.0", "axios": "^0.24.0", "bech32": "^2.0.0", @@ -5054,6 +5054,15 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@vercel/ncc": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", + "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", + "dev": true, + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, "node_modules/@wry/context": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz", @@ -5176,13 +5185,12 @@ } }, "node_modules/algosdk": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.24.1.tgz", - "integrity": "sha512-9moZxdqeJ6GdE4N6fA/GlUP4LrbLZMYcYkt141J4Ss68OfEgH9qW0wBuZ3ZOKEx/xjc5bg7mLP2Gjg7nwrkmww==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-2.7.0.tgz", + "integrity": "sha512-sBE9lpV7bup3rZ+q2j3JQaFAE9JwZvjWKX00vPlG8e9txctXbgLL56jZhSWZndqhDI9oI+0P4NldkuQIWdrUyg==", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", - "buffer": "^6.0.2", - "cross-fetch": "^3.1.5", + "buffer": "^6.0.3", "hi-base32": "^0.5.1", "js-sha256": "^0.9.0", "js-sha3": "^0.8.0", @@ -5192,7 +5200,7 @@ "vlq": "^2.0.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/ansi-escapes": { @@ -6415,17 +6423,6 @@ "tslib": "^2.0.3" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, "node_modules/drbg.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", @@ -12543,9 +12540,9 @@ "dev": true }, "@certusone/wormhole-sdk": { - "version": "0.9.21-beta.0", - "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.9.21-beta.0.tgz", - "integrity": "sha512-44oa/bvVKEtFnIwkg+NdGfVjfyiLdguRBVe4g1EK3Y/Yd6VcPtnETwJU5z5SXJvoV2KnfpnSmK8B+tMKfQ6kbg==", + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@certusone/wormhole-sdk/-/wormhole-sdk-0.10.5.tgz", + "integrity": "sha512-wKONuigkakoFx9HplBt2Jh5KPxc7xgtDJVrIb2/SqYWbFrdpiZrMC4H6kTZq2U4+lWtqaCa1aJ1q+3GOTNx2CQ==", "requires": { "@certusone/wormhole-sdk-proto-web": "0.0.6", "@certusone/wormhole-sdk-wasm": "^0.0.1", @@ -12559,7 +12556,7 @@ "@solana/web3.js": "^1.66.2", "@terra-money/terra.js": "3.1.9", "@xpla/xpla.js": "^0.2.1", - "algosdk": "^1.15.0", + "algosdk": "^2.4.0", "aptos": "1.5.0", "axios": "^0.24.0", "bech32": "^2.0.0", @@ -15523,6 +15520,12 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "@vercel/ncc": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", + "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", + "dev": true + }, "@wry/context": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.3.tgz", @@ -15620,13 +15623,12 @@ "integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==" }, "algosdk": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.24.1.tgz", - "integrity": "sha512-9moZxdqeJ6GdE4N6fA/GlUP4LrbLZMYcYkt141J4Ss68OfEgH9qW0wBuZ3ZOKEx/xjc5bg7mLP2Gjg7nwrkmww==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-2.7.0.tgz", + "integrity": "sha512-sBE9lpV7bup3rZ+q2j3JQaFAE9JwZvjWKX00vPlG8e9txctXbgLL56jZhSWZndqhDI9oI+0P4NldkuQIWdrUyg==", "requires": { "algo-msgpack-with-bigint": "^2.1.1", - "buffer": "^6.0.2", - "cross-fetch": "^3.1.5", + "buffer": "^6.0.3", "hi-base32": "^0.5.1", "js-sha256": "^0.9.0", "js-sha3": "^0.8.0", @@ -16580,11 +16582,6 @@ "tslib": "^2.0.3" } }, - "dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" - }, "drbg.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", diff --git a/blockchain-watcher/package.json b/blockchain-watcher/package.json index 77610ea6..8321fada 100644 --- a/blockchain-watcher/package.json +++ b/blockchain-watcher/package.json @@ -5,9 +5,11 @@ "main": "index.js", "scripts": { "start": "node lib/start.js", + "start:ncc": "node lib/index.js", "test": "jest", "test:coverage": "jest --collectCoverage=true", "build": "tsc", + "build:ncc": "ncc build src/start.ts -o lib", "dev": "ts-node src/start.ts", "dev:debug": "ts-node src/start.ts start --debug --watch", "prettier": "prettier --write ." @@ -16,12 +18,11 @@ "license": "ISC", "dependencies": { "@aws-sdk/client-sns": "^3.445.0", - "@certusone/wormhole-sdk": "^0.9.21-beta.0", + "@certusone/wormhole-sdk": "0.10.5", "@types/config": "^3.3.3", "axios": "^1.6.0", "bs58": "^5.0.0", "config": "^3.3.9", - "dotenv": "^16.3.1", "ethers": "^5", "mollitia": "^0.1.0", "prom-client": "^15.0.0", @@ -33,6 +34,7 @@ "@types/koa-router": "^7.4.4", "@types/uuid": "^9.0.6", "@types/yargs": "^17.0.23", + "@vercel/ncc": "^0.38.1", "jest": "^29.7.0", "nock": "^13.3.8", "prettier": "^2.8.7", @@ -43,5 +45,40 @@ }, "engines": { "node": ">=18.0.0" + }, + "prettier": { + "printWidth": 100 + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "setupFiles": [ + "/src/infrastructure/log.ts" + ], + "roots": [ + "test", + "src" + ], + "testRegex": ".*\\.test\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverage": true, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coveragePathIgnorePatterns": [ + "node_modules", + "test" + ], + "coverageDirectory": "./coverage", + "coverageThreshold": { + "global": { + "lines": 70.85 + } + } } } diff --git a/blockchain-watcher/src/domain/actions/evm/GetEvmLogs.ts b/blockchain-watcher/src/domain/actions/evm/GetEvmLogs.ts new file mode 100644 index 00000000..14e4a83b --- /dev/null +++ b/blockchain-watcher/src/domain/actions/evm/GetEvmLogs.ts @@ -0,0 +1,49 @@ +import { EvmLog } from "../../entities"; +import { EvmBlockRepository } from "../../repositories"; +import winston from "winston"; + +export class GetEvmLogs { + private readonly blockRepo: EvmBlockRepository; + protected readonly logger: winston.Logger; + + constructor(blockRepo: EvmBlockRepository) { + this.blockRepo = blockRepo; + this.logger = winston.child({ module: "GetEvmLogs" }); + } + + async execute(range: Range, opts: GetEvmLogsOpts): Promise { + if (range.fromBlock > range.toBlock) { + this.logger.info( + `[exec] Invalid range [fromBlock: ${range.fromBlock} - toBlock: ${range.toBlock}]` + ); + return []; + } + + const logs = await this.blockRepo.getFilteredLogs(opts.chain, { + fromBlock: range.fromBlock, + toBlock: range.toBlock, + addresses: opts.addresses ?? [], // Works when sending multiple addresses, but not multiple topics. + topics: opts.topics ?? [], + }); + + const blockNumbers = new Set(logs.map((log) => log.blockNumber)); + const blocks = await this.blockRepo.getBlocks(opts.chain, blockNumbers); + logs.forEach((log) => { + const block = blocks[log.blockHash]; + log.blockTime = block.timestamp; + }); + + return logs; + } +} + +type Range = { + fromBlock: bigint; + toBlock: bigint; +}; + +export interface GetEvmLogsOpts { + addresses?: string[]; + topics?: string[]; + chain: string; +} diff --git a/blockchain-watcher/src/domain/actions/evm/PollEvmLogs.ts b/blockchain-watcher/src/domain/actions/evm/PollEvmLogs.ts index 917a4ace..9e4aa1bf 100644 --- a/blockchain-watcher/src/domain/actions/evm/PollEvmLogs.ts +++ b/blockchain-watcher/src/domain/actions/evm/PollEvmLogs.ts @@ -1,5 +1,6 @@ import { EvmLog } from "../../entities"; import { RunPollingJob } from "../RunPollingJob"; +import { GetEvmLogs } from "./GetEvmLogs"; import { EvmBlockRepository, MetadataRepository, StatRepository } from "../../repositories"; import winston from "winston"; @@ -14,6 +15,7 @@ export class PollEvmLogs extends RunPollingJob { private readonly blockRepo: EvmBlockRepository; private readonly metadataRepo: MetadataRepository; private readonly statsRepository: StatRepository; + private readonly getEvmLogs: GetEvmLogs; private cfg: PollEvmLogsConfig; private latestBlockHeight?: bigint; @@ -31,6 +33,7 @@ export class PollEvmLogs extends RunPollingJob { this.metadataRepo = metadataRepo; this.statsRepository = statsRepository; this.cfg = cfg; + this.getEvmLogs = new GetEvmLogs(blockRepo); this.logger = winston.child({ module: "PollEvmLogs", label: this.cfg.id }); } @@ -55,29 +58,17 @@ export class PollEvmLogs extends RunPollingJob { protected async get(): Promise { this.report(); - this.latestBlockHeight = await this.blockRepo.getBlockHeight(this.cfg.getCommitment()); + this.latestBlockHeight = await this.blockRepo.getBlockHeight( + this.cfg.chain, + this.cfg.getCommitment() + ); const range = this.getBlockRange(this.latestBlockHeight); - if (range.fromBlock > this.latestBlockHeight) { - this.logger.info( - `[get] Next range is after latest block height [fromBlock: ${range.fromBlock} - latestBlock: ${this.latestBlockHeight}], waiting...` - ); - return []; - } - - const logs = await this.blockRepo.getFilteredLogs({ - fromBlock: range.fromBlock, - toBlock: range.toBlock, - addresses: this.cfg.addresses, // Works when sending multiple addresses, but not multiple topics. - topics: [], // this.cfg.topics => will be applied by handlers - }); - - const blockNumbers = new Set(logs.map((log) => log.blockNumber)); - const blocks = await this.blockRepo.getBlocks(blockNumbers); - logs.forEach((log) => { - const block = blocks[log.blockHash]; - log.blockTime = block.timestamp; + const logs = await this.getEvmLogs.execute(range, { + chain: this.cfg.chain, + addresses: this.cfg.addresses, + topics: this.cfg.topics, }); this.lastRange = range; @@ -151,13 +142,13 @@ export interface PollEvmLogsConfigProps { addresses: string[]; topics: string[]; id?: string; - chain?: string; + chain: string; } export class PollEvmLogsConfig { private props: PollEvmLogsConfigProps; - constructor(props: PollEvmLogsConfigProps = { addresses: [], topics: [] }) { + constructor(props: PollEvmLogsConfigProps) { if (props.fromBlock && props.toBlock && props.fromBlock > props.toBlock) { throw new Error("fromBlock must be less than or equal to toBlock"); } @@ -213,9 +204,7 @@ export class PollEvmLogsConfig { return this.props.chain; } - static fromBlock(fromBlock: bigint) { - const cfg = new PollEvmLogsConfig(); - cfg.props.fromBlock = fromBlock; - return cfg; + static fromBlock(chain: string, fromBlock: bigint) { + return new PollEvmLogsConfig({ chain, fromBlock, addresses: [], topics: [] }); } } diff --git a/blockchain-watcher/src/domain/actions/index.ts b/blockchain-watcher/src/domain/actions/index.ts index df33d031..936a1db4 100644 --- a/blockchain-watcher/src/domain/actions/index.ts +++ b/blockchain-watcher/src/domain/actions/index.ts @@ -1,5 +1,7 @@ export * from "./evm/HandleEvmLogs"; +export * from "./evm/GetEvmLogs"; export * from "./evm/PollEvmLogs"; +export * from "./solana/GetSolanaTransactions"; export * from "./solana/PollSolanaTransactions"; export * from "./RunPollingJob"; export * from "./StartJobs"; diff --git a/blockchain-watcher/src/domain/actions/solana/GetSolanaTransactions.ts b/blockchain-watcher/src/domain/actions/solana/GetSolanaTransactions.ts new file mode 100644 index 00000000..0f485786 --- /dev/null +++ b/blockchain-watcher/src/domain/actions/solana/GetSolanaTransactions.ts @@ -0,0 +1,79 @@ +import winston from "winston"; +import { RunPollingJob } from "../RunPollingJob"; +import { MetadataRepository, SolanaSlotRepository, StatRepository } from "../../repositories"; +import { solana } from "../../entities"; + +export class GetSolanaTransactions { + private slotRepository: SolanaSlotRepository; + protected logger: winston.Logger; + + constructor(slotRepo: SolanaSlotRepository) { + this.slotRepository = slotRepo; + this.logger = winston.child({ module: "GetSolanaTransactions" }); + } + + async execute( + programId: string, + range: Range, + opts: GetSolanaTxsOpts + ): Promise { + if ( + !range.fromBlock.blockTime || + !range.toBlock.blockTime || + range.fromBlock.blockTime > range.toBlock.blockTime + ) { + throw new Error( + `Invalid slot range: fromSlotBlockTime=${range.fromBlock.blockTime} toSlotBlockTime=${range.toBlock.blockTime}` + ); + } + + // signatures for address goes back from current sig + const afterSignature = range.fromBlock.transactions[0]?.transaction.signatures[0]; + let beforeSignature: string | undefined = + range.toBlock.transactions[range.toBlock.transactions.length - 1]?.transaction.signatures[0]; + if (!afterSignature || !beforeSignature) { + throw new Error( + `No signature presents in transactions. After: ${afterSignature}. Before: ${beforeSignature} [slots: ${range.fromBlock.blockTime} - ${range.toBlock.blockTime}]` + ); + } + + let currentSignaturesCount = opts.signaturesLimit; + + let results: solana.Transaction[] = []; + while (currentSignaturesCount === opts.signaturesLimit && beforeSignature != undefined) { + const sigs: solana.ConfirmedSignatureInfo[] = + await this.slotRepository.getSignaturesForAddress( + programId, + beforeSignature, + afterSignature, + opts.signaturesLimit, + opts.commitment + ); + this.logger.debug( + `Got ${sigs.length} signatures for address ${programId} [blocks: ${ + range.fromBlock.blockTime + } - ${range.toBlock.blockTime}][sigs: ${afterSignature.substring( + 0, + 5 + )} - ${beforeSignature.substring(0, 5)}]]` + ); + + const txs = await this.slotRepository.getTransactions(sigs, opts.commitment); + results.push(...txs); + currentSignaturesCount = sigs.length; + beforeSignature = sigs.at(-1)?.signature; + } + + return results; + } +} + +export type GetSolanaTxsOpts = { + commitment: string; + signaturesLimit: number; +}; + +type Range = { + fromBlock: solana.Block; + toBlock: solana.Block; +}; diff --git a/blockchain-watcher/src/domain/actions/solana/PollSolanaTransactions.ts b/blockchain-watcher/src/domain/actions/solana/PollSolanaTransactions.ts index aa5dfd09..864447f6 100644 --- a/blockchain-watcher/src/domain/actions/solana/PollSolanaTransactions.ts +++ b/blockchain-watcher/src/domain/actions/solana/PollSolanaTransactions.ts @@ -1,5 +1,6 @@ import winston from "winston"; import { RunPollingJob } from "../RunPollingJob"; +import { GetSolanaTransactions } from "./GetSolanaTransactions"; import { MetadataRepository, SolanaSlotRepository, StatRepository } from "../../repositories"; import { solana } from "../../entities"; @@ -8,6 +9,7 @@ export class PollSolanaTransactions extends RunPollingJob { private metadataRepo: MetadataRepository; private slotRepository: SolanaSlotRepository; private statsRepo: StatRepository; + private getSolanaTransactions: GetSolanaTransactions; private latestSlot?: number; private slotCursor?: number; private lastRange?: Range; @@ -23,6 +25,7 @@ export class PollSolanaTransactions extends RunPollingJob { this.metadataRepo = metadataRepo; this.slotRepository = slotRepo; + this.getSolanaTransactions = new GetSolanaTransactions(slotRepo); this.statsRepo = statsRepo; this.cfg = cfg; this.logger = winston.child({ module: "PollSolanaTransactions", label: this.cfg.id }); @@ -78,32 +81,14 @@ export class PollSolanaTransactions extends RunPollingJob { ); } - let currentSignaturesCount = this.cfg.signaturesLimit; - - let results: solana.Transaction[] = []; - while (currentSignaturesCount === this.cfg.signaturesLimit && beforeSignature != undefined) { - const sigs: solana.ConfirmedSignatureInfo[] = - await this.slotRepository.getSignaturesForAddress( - this.cfg.programId, - beforeSignature, - afterSignature, - this.cfg.signaturesLimit, - this.cfg.commitment - ); - this.logger.debug( - `Got ${sigs.length} signatures for address ${this.cfg.programId} [slots: ${ - range.fromSlot - } - ${range.toSlot}][sigs: ${afterSignature.substring(0, 5)} - ${beforeSignature.substring( - 0, - 5 - )}]]` - ); - - const txs = await this.slotRepository.getTransactions(sigs, this.cfg.commitment); - results.push(...txs); - currentSignaturesCount = sigs.length; - beforeSignature = sigs.at(-1)?.signature; - } + const results: solana.Transaction[] = await this.getSolanaTransactions.execute( + this.cfg.programId, + { fromBlock, toBlock }, + { + commitment: this.cfg.commitment, + signaturesLimit: this.cfg.signaturesLimit, + } + ); this.lastRange = range; return results; diff --git a/blockchain-watcher/src/domain/repositories.ts b/blockchain-watcher/src/domain/repositories.ts index 4b05478a..cf8e8059 100644 --- a/blockchain-watcher/src/domain/repositories.ts +++ b/blockchain-watcher/src/domain/repositories.ts @@ -4,9 +4,9 @@ import { ConfirmedSignatureInfo } from "./entities/solana"; import { Fallible, SolanaFailure } from "./errors"; export interface EvmBlockRepository { - getBlockHeight(finality: string): Promise; - getBlocks(blockNumbers: Set): Promise>; - getFilteredLogs(filter: EvmLogFilter): Promise; + getBlockHeight(chain: string, finality: string): Promise; + getBlocks(chain: string, blockNumbers: Set): Promise>; + getFilteredLogs(chain: string, filter: EvmLogFilter): Promise; } export interface SolanaSlotRepository { diff --git a/blockchain-watcher/src/infrastructure/config.ts b/blockchain-watcher/src/infrastructure/config.ts index aeaa3692..2992aa8f 100644 --- a/blockchain-watcher/src/infrastructure/config.ts +++ b/blockchain-watcher/src/infrastructure/config.ts @@ -13,11 +13,11 @@ export type Config = { jobs: { dir: string; }; - platforms: Record; + chains: Record; supportedChains: string[]; }; -export type PlatformConfig = { +export type ChainRPCConfig = { name: string; network: string; chainId: number; @@ -49,6 +49,6 @@ export const configuration = { jobs: { dir: config.get("jobs.dir"), }, - platforms: config.get>("platforms"), + chains: config.get>("chains"), supportedChains: config.get("supportedChains"), } as Config; diff --git a/blockchain-watcher/src/infrastructure/mappers/solanaLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/solanaLogMessagePublishedMapper.ts index 109d0dc2..fb09af34 100644 --- a/blockchain-watcher/src/infrastructure/mappers/solanaLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/solanaLogMessagePublishedMapper.ts @@ -5,7 +5,7 @@ import { solana, LogFoundEvent, LogMessagePublished } from "../../domain/entitie import { CompiledInstruction, MessageCompiledInstruction } from "../../domain/entities/solana"; import { configuration } from "../config"; -const connection = new Connection(configuration.platforms.solana.rpcs[0]); // TODO: should be better to inject this to improve testability +const connection = new Connection(configuration.chains.solana.rpcs[0]); // TODO: should be better to inject this to improve testability export const solanaLogMessagePublishedMapper = async ( tx: solana.Transaction, diff --git a/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts b/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts index a56b2752..ff56c9b7 100644 --- a/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts +++ b/blockchain-watcher/src/infrastructure/repositories/EvmJsonRPCBlockRepository.ts @@ -1,8 +1,9 @@ import { EvmBlock, EvmLogFilter, EvmLog, EvmTag } from "../../domain/entities"; import { EvmBlockRepository } from "../../domain/repositories"; import winston from "../log"; -import { HttpClient } from "../http/HttpClient"; +import { HttpClient } from "../rpc/http/HttpClient"; import { HttpClientError } from "../errors/HttpClientError"; +import { ChainRPCConfig } from "../config"; /** * EvmJsonRPCBlockRepository is a repository that uses a JSON RPC endpoint to fetch blocks. @@ -13,20 +14,19 @@ const HEXADECIMAL_PREFIX = "0x"; export class EvmJsonRPCBlockRepository implements EvmBlockRepository { private httpClient: HttpClient; - private chainId: number; - private rpc: URL; + private cfg: EvmJsonRPCBlockRepositoryCfg; private readonly logger; constructor(cfg: EvmJsonRPCBlockRepositoryCfg, httpClient: HttpClient) { this.httpClient = httpClient; - this.chainId = cfg.chainId; - this.rpc = new URL(cfg.rpc); - this.logger = winston.child({ module: "EvmJsonRPCBlockRepository", chain: cfg.chain }); - this.logger.info(`Using RPC node ${this.rpc.hostname}`); + this.cfg = cfg; + + this.logger = winston.child({ module: "EvmJsonRPCBlockRepository" }); + this.logger.info(`Created for ${Object.keys(this.cfg.chains)}`); } - async getBlockHeight(finality: EvmTag): Promise { - const block: EvmBlock = await this.getBlock(finality); + async getBlockHeight(chain: string, finality: EvmTag): Promise { + const block: EvmBlock = await this.getBlock(chain, finality); return block.number; } @@ -35,7 +35,7 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { * @param blockNumbers * @returns a record of block hash -> EvmBlock */ - async getBlocks(blockNumbers: Set): Promise> { + async getBlocks(chain: string, blockNumbers: Set): Promise> { if (!blockNumbers.size) return {}; const reqs: any[] = []; @@ -51,11 +51,12 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { }); } + const chainCfg = this.getCurrentChain(chain); let results: (undefined | { id: string; result?: EvmBlock; error?: ErrorBlock })[]; try { - results = await this.httpClient.post(this.rpc.href, reqs); + results = await this.httpClient.post(chainCfg.rpc.href, reqs); } catch (e: HttpClientError | any) { - this.handleError(e, "eth_getBlockByNumber"); + this.handleError(chain, e, "eth_getBlockByNumber"); throw e; } @@ -72,7 +73,6 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { (response && response.result === null) || (response?.error && response.error?.code && response.error.code === 6969) ) { - this.logger.warn; return { hash: "", number: BigInt(response.id), @@ -92,14 +92,16 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { }; } - const msg = `[getBlocks] Got error ${ + const msg = `[${chain}][getBlocks] Got error ${ response?.error?.message - } for eth_getBlockByNumber for ${response?.id ?? reqs[idx].id} on ${this.rpc.hostname}`; + } for eth_getBlockByNumber for ${response?.id ?? reqs[idx].id} on ${ + chainCfg.rpc.hostname + }`; this.logger.error(msg); throw new Error( - `Unable to parse result of eth_getBlockByNumber for ${ + `Unable to parse result of eth_getBlockByNumber[${chain}] for ${ response?.id ?? reqs[idx].id }: ${msg}` ); @@ -114,11 +116,11 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { throw new Error( `Unable to parse ${ results?.length ?? 0 - } blocks for eth_getBlockByNumber for numbers ${blockNumbers} on ${this.rpc.hostname}` + } blocks for eth_getBlockByNumber for numbers ${blockNumbers} on ${chainCfg.rpc.hostname}` ); } - async getFilteredLogs(filter: EvmLogFilter): Promise { + async getFilteredLogs(chain: string, filter: EvmLogFilter): Promise { const parsedFilters = { topics: filter.topics, address: filter.addresses, @@ -126,31 +128,32 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { toBlock: `${HEXADECIMAL_PREFIX}${filter.toBlock.toString(16)}`, }; + const chainCfg = this.getCurrentChain(chain); let response: { result: Log[]; error?: ErrorBlock }; try { - response = await this.httpClient.post(this.rpc.href, { + response = await this.httpClient.post(chainCfg.rpc.href, { jsonrpc: "2.0", method: "eth_getLogs", params: [parsedFilters], id: 1, }); } catch (e: HttpClientError | any) { - this.handleError(e, "eth_getLogs"); + this.handleError(chain, e, "eth_getLogs"); throw e; } const logs = response?.result; this.logger.info( - `[getFilteredLogs] Got ${logs?.length} logs for ${this.describeFilter(filter)} from ${ - this.rpc.hostname - }` + `[${chain}][getFilteredLogs] Got ${logs?.length} logs for ${this.describeFilter( + filter + )} from ${chainCfg.rpc.hostname}` ); return logs.map((log) => ({ ...log, blockNumber: BigInt(log.blockNumber), transactionIndex: log.transactionIndex.toString(), - chainId: this.chainId, + chainId: chainCfg.chainId, })); } @@ -161,17 +164,18 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { /** * Loosely based on the wormhole-dashboard implementation (minus some specially crafted blocks when null result is obtained) */ - private async getBlock(blockNumberOrTag: EvmTag): Promise { + private async getBlock(chain: string, blockNumberOrTag: EvmTag): Promise { + const chainCfg = this.getCurrentChain(chain); let response: { result?: EvmBlock; error?: ErrorBlock }; try { - response = await this.httpClient.post(this.rpc.href, { + response = await this.httpClient.post(chainCfg.rpc.href, { jsonrpc: "2.0", method: "eth_getBlockByNumber", params: [blockNumberOrTag, false], // this means we'll get a light block (no txs) id: 1, }); } catch (e: HttpClientError | any) { - this.handleError(e, "eth_getBlockByNumber"); + this.handleError(chain, e, "eth_getBlockByNumber"); throw e; } @@ -186,28 +190,36 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository { }; } throw new Error( - `Unable to parse result of eth_getBlockByNumber for ${blockNumberOrTag} on ${this.rpc}` + `Unable to parse result of eth_getBlockByNumber for ${blockNumberOrTag} on ${chainCfg.rpc}` ); } - private handleError(e: any, method: string) { + private handleError(chain: string, e: any, method: string) { + const chainCfg = this.getCurrentChain(chain); if (e instanceof HttpClientError) { this.logger.error( - `[getBlock] Got ${e.status} from ${this.rpc.hostname}/${method}. ${ + `[${chain}][getBlock] Got ${e.status} from ${chainCfg.rpc.hostname}/${method}. ${ e?.message ?? `${e?.message}` }` ); } else { - this.logger.error(`[getBlock] Got error ${e} from ${this.rpc.hostname}/${method}`); + this.logger.error( + `[${chain}][getBlock] Got error ${e} from ${chainCfg.rpc.hostname}/${method}` + ); } } + + private getCurrentChain(chain: string) { + const cfg = this.cfg.chains[chain]; + return { + chainId: cfg.chainId, + rpc: new URL(cfg.rpcs[0]), + }; + } } export type EvmJsonRPCBlockRepositoryCfg = { - rpc: string; - timeout?: number; - chain: string; - chainId: number; + chains: Record; }; type ErrorBlock = { diff --git a/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts similarity index 81% rename from blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts rename to blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts index b92e08e6..9291fbf6 100644 --- a/blockchain-watcher/src/infrastructure/RepositoriesBuilder.ts +++ b/blockchain-watcher/src/infrastructure/repositories/RepositoriesBuilder.ts @@ -1,6 +1,6 @@ import { SNSClient, SNSClientConfig } from "@aws-sdk/client-sns"; import { Connection } from "@solana/web3.js"; -import { Config } from "./config"; +import { Config } from "../config"; import { SnsEventRepository, EvmJsonRPCBlockRepository, @@ -10,9 +10,9 @@ import { StaticJobRepository, Web3SolanaSlotRepository, RateLimitedSolanaSlotRepository, -} from "./repositories"; -import { HttpClient } from "./http/HttpClient"; -import { JobRepository } from "../domain/repositories"; +} from "."; +import { HttpClient } from "../rpc/http/HttpClient"; +import { JobRepository } from "../../domain/repositories"; const SOLANA_CHAIN = "solana"; const EVM_CHAINS = ["ethereum", "avalanche", "fantom", "karura", "acala"]; @@ -37,10 +37,10 @@ export class RepositoriesBuilder { this.repositories.set("metadata", new FileMetadataRepository(this.cfg.metadata.dir)); this.cfg.supportedChains.forEach((chain) => { - if (!this.cfg.platforms[chain]) throw new Error(`No config for chain ${chain}`); + if (!this.cfg.chains[chain]) throw new Error(`No config for chain ${chain}`); if (chain === SOLANA_CHAIN) { - const cfg = this.cfg.platforms[chain]; + const cfg = this.cfg.chains[chain]; const solanaSlotRepository = new RateLimitedSolanaSlotRepository( new Web3SolanaSlotRepository( new Connection(cfg.rpcs[0], { disableRetryOnRateLimit: true }) @@ -50,18 +50,12 @@ export class RepositoriesBuilder { this.repositories.set("solana-slotRepo", solanaSlotRepository); } - if (EVM_CHAINS.includes(chain)) { - const httpClient = this.createHttpClient(this.cfg.platforms[chain].timeout); + if (!this.repositories.has("evmRepo")) { + const httpClient = this.createHttpClient(this.cfg.chains[chain].timeout); const repoCfg: EvmJsonRPCBlockRepositoryCfg = { - chain, - chainId: this.cfg.platforms[chain].chainId, - rpc: this.cfg.platforms[chain].rpcs[0], - timeout: this.cfg.platforms[chain].timeout, + chains: this.cfg.chains, }; - this.repositories.set( - `${chain}-evmRepo`, - new EvmJsonRPCBlockRepository(repoCfg, httpClient) - ); + this.repositories.set("evmRepo", new EvmJsonRPCBlockRepository(repoCfg, httpClient)); } }); @@ -82,7 +76,8 @@ export class RepositoriesBuilder { } public getEvmBlockRepository(chain: string): EvmJsonRPCBlockRepository { - return this.getRepo(`${chain}-evmRepo`); + if (!EVM_CHAINS.includes(chain)) throw new Error(`Chain ${chain} not supported`); + return this.getRepo("evmRepo"); } public getSnsEventRepository(): SnsEventRepository { diff --git a/blockchain-watcher/src/infrastructure/rpc/HealthController.ts b/blockchain-watcher/src/infrastructure/rpc/http/HealthController.ts similarity index 78% rename from blockchain-watcher/src/infrastructure/rpc/HealthController.ts rename to blockchain-watcher/src/infrastructure/rpc/http/HealthController.ts index ec28f12a..1482cc61 100644 --- a/blockchain-watcher/src/infrastructure/rpc/HealthController.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/HealthController.ts @@ -1,4 +1,4 @@ -import { StatRepository } from "../../domain/repositories"; +import { StatRepository } from "../../../domain/repositories"; export class HealthController { private readonly statsRepo: StatRepository; diff --git a/blockchain-watcher/src/infrastructure/http/HttpClient.ts b/blockchain-watcher/src/infrastructure/rpc/http/HttpClient.ts similarity index 97% rename from blockchain-watcher/src/infrastructure/http/HttpClient.ts rename to blockchain-watcher/src/infrastructure/rpc/http/HttpClient.ts index 275fd78e..2b5d7705 100644 --- a/blockchain-watcher/src/infrastructure/http/HttpClient.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/HttpClient.ts @@ -1,6 +1,6 @@ import axios, { AxiosError, AxiosInstance } from "axios"; import { setTimeout } from "timers/promises"; -import { HttpClientError } from "../errors/HttpClientError"; +import { HttpClientError } from "../../errors/HttpClientError"; /** * A simple HTTP client with exponential backoff retries and 429 handling. diff --git a/blockchain-watcher/src/infrastructure/rpc/Server.ts b/blockchain-watcher/src/infrastructure/rpc/http/Server.ts similarity index 96% rename from blockchain-watcher/src/infrastructure/rpc/Server.ts rename to blockchain-watcher/src/infrastructure/rpc/http/Server.ts index c3f2dd57..85f64155 100644 --- a/blockchain-watcher/src/infrastructure/rpc/Server.ts +++ b/blockchain-watcher/src/infrastructure/rpc/http/Server.ts @@ -1,7 +1,7 @@ import http from "http"; import url from "url"; import { HealthController } from "./HealthController"; -import log from "../log"; +import log from "../../log"; export class WebServer { private server: http.Server; diff --git a/blockchain-watcher/src/start.ts b/blockchain-watcher/src/start.ts index 5ba623f7..cfce6d18 100644 --- a/blockchain-watcher/src/start.ts +++ b/blockchain-watcher/src/start.ts @@ -1,8 +1,8 @@ import { configuration } from "./infrastructure/config"; -import { RepositoriesBuilder } from "./infrastructure/RepositoriesBuilder"; +import { RepositoriesBuilder } from "./infrastructure/repositories/RepositoriesBuilder"; import log from "./infrastructure/log"; -import { WebServer } from "./infrastructure/rpc/Server"; -import { HealthController } from "./infrastructure/rpc/HealthController"; +import { WebServer } from "./infrastructure/rpc/http/Server"; +import { HealthController } from "./infrastructure/rpc/http/HealthController"; import { StartJobs } from "./domain/actions"; let repos: RepositoriesBuilder; diff --git a/blockchain-watcher/test/domain/actions/evm/PollEvmLogs.test.ts b/blockchain-watcher/test/domain/actions/evm/PollEvmLogs.test.ts index b213cb63..8c96ac0f 100644 --- a/blockchain-watcher/test/domain/actions/evm/PollEvmLogs.test.ts +++ b/blockchain-watcher/test/domain/actions/evm/PollEvmLogs.test.ts @@ -12,7 +12,7 @@ import { } from "../../../../src/domain/repositories"; import { EvmBlock, EvmLog } from "../../../../src/domain/entities"; -let cfg = PollEvmLogsConfig.fromBlock(0n); +let cfg = PollEvmLogsConfig.fromBlock("acala", 0n); let getBlocksSpy: jest.SpiedFunction; let getLogsSpy: jest.SpiedFunction; @@ -46,9 +46,13 @@ describe("PollEvmLogs", () => { await thenWaitForAssertion( () => expect(getBlocksSpy).toHaveReturnedTimes(1), - () => expect(getBlocksSpy).toHaveBeenCalledWith(new Set([currentHeight, currentHeight + 1n])), () => - expect(getLogsSpy).toBeCalledWith({ + expect(getBlocksSpy).toHaveBeenCalledWith( + "acala", + new Set([currentHeight, currentHeight + 1n]) + ), + () => + expect(getLogsSpy).toBeCalledWith("acala", { addresses: cfg.addresses, topics: cfg.topics, fromBlock: currentHeight + blocksAhead, @@ -73,7 +77,7 @@ describe("PollEvmLogs", () => { new Set([lastExtractedBlock, lastExtractedBlock + 1n]) ), () => - expect(getLogsSpy).toBeCalledWith({ + expect(getLogsSpy).toBeCalledWith("acala", { addresses: cfg.addresses, topics: cfg.topics, fromBlock: lastExtractedBlock + 1n, diff --git a/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts b/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts index f0288632..80e5a477 100644 --- a/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/EvmJsonRPCBlockRepository.integration.test.ts @@ -3,9 +3,10 @@ import { EvmJsonRPCBlockRepository } from "../../../src/infrastructure/repositor import axios from "axios"; import nock from "nock"; import { EvmLogFilter, EvmTag } from "../../../src/domain/entities"; -import { HttpClient } from "../../../src/infrastructure/http/HttpClient"; +import { HttpClient } from "../../../src/infrastructure/rpc/http/HttpClient"; axios.defaults.adapter = "http"; // needed by nock +const eth = "ethereum"; const rpc = "http://localhost"; const address = "0x98f3c9e6e3face36baad05fe09d375ef1464288b"; const topic = "0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2"; @@ -27,7 +28,7 @@ describe("EvmJsonRPCBlockRepository", () => { givenARepo(); givenBlockHeightIs(expectedHeight, "latest"); - const result = await repo.getBlockHeight("latest"); + const result = await repo.getBlockHeight(eth, "latest"); expect(result).toBe(expectedHeight); }); @@ -37,7 +38,7 @@ describe("EvmJsonRPCBlockRepository", () => { givenARepo(); givenBlocksArePresent(blockNumbers); - const result = await repo.getBlocks(new Set(blockNumbers)); + const result = await repo.getBlocks(eth, new Set(blockNumbers)); expect(Object.keys(result)).toHaveLength(blockNumbers.length); blockNumbers.forEach((blockNumber) => { @@ -55,7 +56,7 @@ describe("EvmJsonRPCBlockRepository", () => { givenLogsPresent(filter); - const logs = await repo.getFilteredLogs(filter); + const logs = await repo.getFilteredLogs(eth, filter); expect(logs).toHaveLength(1); expect(logs[0].blockNumber).toBe(1n); @@ -66,7 +67,11 @@ describe("EvmJsonRPCBlockRepository", () => { const givenARepo = () => { repo = new EvmJsonRPCBlockRepository( - { rpc, timeout: 100, chain: "ethereum", chainId: 2 }, + { + chains: { + ethereum: { rpcs: [rpc], timeout: 100, name: "ethereum", network: "mainnet", chainId: 2 }, + }, + }, new HttpClient() ); }; diff --git a/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts index 4191ce88..02f8cb4a 100644 --- a/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts +++ b/blockchain-watcher/test/infrastructure/repositories/RepositoriesBuilder.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "@jest/globals"; -import { RepositoriesBuilder } from "../../../src/infrastructure/RepositoriesBuilder"; +import { RepositoriesBuilder } from "../../../src/infrastructure/repositories/RepositoriesBuilder"; import { configMock } from "../../mocks/configMock"; import { EvmJsonRPCBlockRepository, diff --git a/blockchain-watcher/test/mocks/configMock.ts b/blockchain-watcher/test/mocks/configMock.ts index 201686ec..181f95e8 100644 --- a/blockchain-watcher/test/mocks/configMock.ts +++ b/blockchain-watcher/test/mocks/configMock.ts @@ -1,8 +1,8 @@ import { SnsConfig } from "../../src/infrastructure/repositories"; -import { Config, PlatformConfig } from "../../src/infrastructure/config"; +import { Config, ChainRPCConfig } from "../../src/infrastructure/config"; export const configMock = (chains: string[] = []): Config => { - const platformRecord: Record = { + const platformRecord: Record = { solana: { name: "solana", network: "devnet", @@ -71,7 +71,7 @@ export const configMock = (chains: string[] = []): Config => { jobs: { dir: "./metadata-repo/jobs", }, - platforms: platformRecord, + chains: platformRecord, supportedChains: chains, }; diff --git a/deploy/blockchain-watcher/env/prod-mainnet.env b/deploy/blockchain-watcher/env/prod-mainnet.env index f44a6d11..0a5abe9e 100644 --- a/deploy/blockchain-watcher/env/prod-mainnet.env +++ b/deploy/blockchain-watcher/env/prod-mainnet.env @@ -1,4 +1,4 @@ -NODE_ENV=prod-mainnet +NODE_ENV=mainnet BLOCKCHAIN_ENV=mainnet NAMESPACE=wormscan NAME=blockchain-watcher diff --git a/deploy/blockchain-watcher/env/prod-testnet.env b/deploy/blockchain-watcher/env/prod-testnet.env index 50c82ee9..9803031b 100644 --- a/deploy/blockchain-watcher/env/prod-testnet.env +++ b/deploy/blockchain-watcher/env/prod-testnet.env @@ -1,4 +1,4 @@ -NODE_ENV=prod-testnet +NODE_ENV=testnet BLOCKCHAIN_ENV=testnet NAMESPACE=wormscan-testnet NAME=blockchain-watcher diff --git a/deploy/blockchain-watcher/env/staging-mainnet.env b/deploy/blockchain-watcher/env/staging-mainnet.env index 85c8695d..4af0258f 100644 --- a/deploy/blockchain-watcher/env/staging-mainnet.env +++ b/deploy/blockchain-watcher/env/staging-mainnet.env @@ -1,4 +1,4 @@ -NODE_ENV=staging-mainnet +NODE_ENV=mainnet BLOCKCHAIN_ENV=mainnet NAMESPACE=wormscan NAME=blockchain-watcher diff --git a/deploy/blockchain-watcher/workers/ethereum.yaml b/deploy/blockchain-watcher/workers/ethereum.yaml index a7e23f33..37daff86 100644 --- a/deploy/blockchain-watcher/workers/ethereum.yaml +++ b/deploy/blockchain-watcher/workers/ethereum.yaml @@ -47,8 +47,7 @@ data: "commitment": "latest", "interval": 15000, "addresses": ["0x706abc4E45D419950511e474C7B9Ed348A4a716c"], - "chain": "ethereum", - "topics": [] + "chain": "ethereum" } }, "handlers": [ @@ -78,8 +77,7 @@ data: "addresses": [ "0xE4eacc10990ba3308DdCC72d985f2a27D20c7d03" ], - "chain": "karura", - "topics": [] + "chain": "karura" } }, "handlers": [ @@ -113,8 +111,7 @@ data: "addresses": [ "0x1BB3B4119b7BA9dfad76B0545fb3F531383c3bB7" ], - "chain": "fantom", - "topics": [] + "chain": "fantom" } }, "handlers": [ @@ -148,8 +145,7 @@ data: "addresses": [ "0x4377B49d559c0a9466477195C6AdC3D433e265c0" ], - "chain": "acala", - "topics": [] + "chain": "acala" } }, "handlers": [ @@ -219,8 +215,7 @@ data: "commitment": "latest", "interval": 5000, "addresses": ["0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B"], - "chain": "ethereum", - "topics": [] + "chain": "ethereum" } }, "handlers": [ @@ -250,8 +245,7 @@ data: "addresses": [ "0xa321448d90d4e5b0A732867c18eA198e75CAC48E" ], - "chain": "karura", - "topics": [] + "chain": "karura" } }, "handlers": [ @@ -285,8 +279,7 @@ data: "addresses": [ "0x126783A6Cb203a3E35344528B26ca3a0489a1485" ], - "chain": "fantom", - "topics": [] + "chain": "fantom" } }, "handlers": [ @@ -320,8 +313,7 @@ data: "addresses": [ "0xa321448d90d4e5b0A732867c18eA198e75CAC48E" ], - "chain": "acala", - "topics": [] + "chain": "acala" } }, "handlers": [