diff --git a/.changeset/silver-suns-deny.md b/.changeset/silver-suns-deny.md new file mode 100644 index 00000000..3e2fde0b --- /dev/null +++ b/.changeset/silver-suns-deny.md @@ -0,0 +1,5 @@ +--- +'@rosen-bridge/watcher': minor +--- + +Integrate Handshake watcher diff --git a/config/default.yaml b/config/default.yaml index 540d78b6..e81cc65f 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -87,6 +87,16 @@ firo: timeout: 10 # rpc request timeout (in seconds) # username: '' # rpc username for authentication required instances # password: '' # rpc password for authentication required instances +handshake: + type: 'rpc' # options: rpc + initial: + height: -1 # initial height of scanning + interval: 180 # scanning interval (in seconds) + rpc: + url: '' # rpc url + timeout: 10 # rpc request timeout (in seconds) + # username: '' # rpc username for authentication required instances + # password: '' # rpc password for authentication required instances ergo: network: 'Mainnet' # ergo network type. testnet or mainnet type: 'node' # ergo scanner type. options: node, explorer diff --git a/docker/custom-environment-variables.yaml b/docker/custom-environment-variables.yaml index 2847b00b..9c2283ca 100644 --- a/docker/custom-environment-variables.yaml +++ b/docker/custom-environment-variables.yaml @@ -34,4 +34,8 @@ firo: rpc: username: 'FIRO_RPC_USERNAME' password: 'FIRO_RPC_PASSWORD' +handshake: + rpc: + username: 'HANDSHAKE_RPC_USERNAME' + password: 'HANDSHAKE_RPC_PASSWORD' overrideLokiBasicAuth: 'OVERRIDE_LOKI_BASIC_AUTH' diff --git a/package-lock.json b/package-lock.json index 6b352ef3..7081cd1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,8 @@ "@rosen-bridge/extended-typeorm": "1.1.0", "@rosen-bridge/firo-observation-extractor": "^1.0.0", "@rosen-bridge/firo-scanner": "^0.1.2", + "@rosen-bridge/handshake-observation-extractor": "^0.1.1", + "@rosen-bridge/handshake-scanner": "^0.1.2", "@rosen-bridge/health-check": "^8.0.0", "@rosen-bridge/json-bigint": "^1.1.0", "@rosen-bridge/log-level-check": "^4.0.0", @@ -3977,6 +3979,283 @@ "npm": "11.6.2" } }, + "node_modules/@rosen-bridge/handshake-observation-extractor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rosen-bridge/handshake-observation-extractor/-/handshake-observation-extractor-0.1.1.tgz", + "integrity": "sha512-CwSyr3U11TyDYm2YPwhCXeBsxs7aEjuzYAPCHzqayRRO7zhaREB22T3Q0EOblU8SKcEGDYPslH/MFqeia+1Giw==", + "license": "MIT", + "dependencies": { + "@rosen-bridge/abstract-logger": "^4.0.0", + "@rosen-bridge/abstract-observation-extractor": "^1.0.4", + "@rosen-bridge/extended-typeorm": "^1.0.1", + "@rosen-bridge/handshake-scanner": "^0.1.1", + "@rosen-bridge/rosen-extractor": "^11.3.0", + "@rosen-bridge/scanner-interfaces": "^0.2.2", + "@rosen-bridge/tokens": "^6.0.0", + "blakejs": "^1.2.1" + }, + "engines": { + "node": ">=22.18.0", + "npm": "11.6.2" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/@blockfrost/openapi": { + "version": "0.1.86", + "resolved": "https://registry.npmjs.org/@blockfrost/openapi/-/openapi-0.1.86.tgz", + "integrity": "sha512-VWFshFtUVmg27jn/dSc+7FvGbGyuHBUyNCqyE4ah9EkE3+ZcF3mIC095TE+f8uS/4lMIryUEGBq/p2zrfF+dEw==", + "dependencies": { + "ajv": "^8.17.1", + "cbor": "^9.0.2", + "rimraf": "6.0.1", + "yaml": "^2.6.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/@rosen-bridge/address-codec": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rosen-bridge/address-codec/-/address-codec-1.2.0.tgz", + "integrity": "sha512-p0UgMkCGMjVabWWQxcYSyCE0VJ8IdZkIxMSOq9JUbqLtbiJJ2ZNjrpJ+jPhWavdS6eg1iMr2j5q+krOFHWhXyw==", + "license": "MIT", + "dependencies": { + "@bitcoinerlab/secp256k1": "^1.2.0", + "@emurgo/cardano-serialization-lib-nodejs": "^13.2.1", + "bitcoinjs-lib": "^6.1.5", + "ergo-lib-wasm-nodejs": "^0.24.1", + "ethers": "6.16.0" + }, + "engines": { + "node": ">=22.18.0", + "npm": "11.6.2" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/@rosen-bridge/rosen-extractor": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@rosen-bridge/rosen-extractor/-/rosen-extractor-11.3.0.tgz", + "integrity": "sha512-NMYttFxUKBAckytK/IvOcwDUTgGZm4RQA7YT8UZ4/5n99AmTWBSc6wTWmSR3quLIC1dkf9QYsMYaf5YzNKFLRA==", + "license": "MIT", + "dependencies": { + "@bitcoinerlab/secp256k1": "^1.2.0", + "@blockfrost/openapi": "^0.1.80", + "@cardano-ogmios/schema": "^6.0.3", + "@emurgo/cardano-serialization-lib-nodejs": "^13.2.1", + "@rosen-bridge/abstract-logger": "^4.0.0", + "@rosen-bridge/address-codec": "^1.2.0", + "@rosen-bridge/json-bigint": "^1.1.0", + "@rosen-bridge/tokens": "^6.0.0", + "bitcoinjs-lib": "^6.1.5", + "ergo-lib-wasm-nodejs": "^0.24.1", + "ethers": "6.16.0", + "lodash-es": "^4.17.21" + }, + "engines": { + "node": ">=22.18.0", + "npm": "11.6.2" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-observation-extractor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rosen-bridge/handshake-scanner": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@rosen-bridge/handshake-scanner/-/handshake-scanner-0.1.2.tgz", + "integrity": "sha512-2qoOrrhgVYX4aPG6kuvHeNU3ow+3q6YP7BAfV7hjUaEQFqzB6/IboFuuPX47Zvzlr99k3zdVFIc/nzL177lDvQ==", + "license": "MIT", + "dependencies": { + "@rosen-bridge/abstract-scanner": "^1.0.4", + "@rosen-bridge/scanner-interfaces": "^0.2.2", + "@rosen-clients/rate-limited-axios": "^2.0.0" + }, + "engines": { + "node": ">=22.18.0", + "npm": "11.6.2" + } + }, "node_modules/@rosen-bridge/health-check": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@rosen-bridge/health-check/-/health-check-8.0.0.tgz", diff --git a/package.json b/package.json index 82c02fad..fdcecd7e 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,8 @@ "@rosen-bridge/extended-typeorm": "1.1.0", "@rosen-bridge/firo-observation-extractor": "^1.0.0", "@rosen-bridge/firo-scanner": "^0.1.2", + "@rosen-bridge/handshake-observation-extractor": "^0.1.1", + "@rosen-bridge/handshake-scanner": "^0.1.2", "@rosen-bridge/health-check": "^8.0.0", "@rosen-bridge/json-bigint": "^1.1.0", "@rosen-bridge/log-level-check": "^4.0.0", diff --git a/src/config/config.ts b/src/config/config.ts index aab91b34..36781168 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,15 +1,15 @@ +import { ErgoNetworkType } from '@rosen-bridge/scanner-interfaces'; +import { TransportOptions } from '@rosen-bridge/winston-logger'; +import { RateLimitedAxiosConfig } from '@rosen-clients/rate-limited-axios'; +import { generateMnemonic } from 'bip39'; import config from 'config'; import * as wasm from 'ergo-lib-wasm-nodejs'; -import { SecretError } from '../errors/errors'; -import * as Constants from './constants'; -import { RosenConfig } from './rosenConfig'; import { cloneDeep } from 'lodash-es'; +import { SecretError } from '../errors/errors'; import { NetworkType } from '../types'; -import { generateMnemonic } from 'bip39'; import { convertMnemonicToSecretKey } from '../utils/utils'; -import { ErgoNetworkType } from '@rosen-bridge/scanner-interfaces'; -import { TransportOptions } from '@rosen-bridge/winston-logger'; -import { RateLimitedAxiosConfig } from '@rosen-clients/rate-limited-axios'; +import * as Constants from './constants'; +import { RosenConfig } from './rosenConfig'; const supportedNetworks: Array = [ Constants.ERGO_CHAIN_NAME, @@ -19,6 +19,7 @@ const supportedNetworks: Array = [ Constants.DOGE_CHAIN_NAME, Constants.ETHEREUM_CHAIN_NAME, Constants.BINANCE_CHAIN_NAME, + Constants.HANDSHAKE_CHAIN_NAME, Constants.FIRO_CHAIN_NAME, ]; @@ -31,6 +32,7 @@ interface ConfigType { binance: BinanceConfig; firo: FiroConfig; doge: DogeConfig; + handshake: HandshakeConfig; general: Config; rosen: RosenConfig; database: DatabaseConfig; @@ -239,6 +241,7 @@ class Config { [Constants.ETHEREUM_CHAIN_NAME]: Constants.ETHEREUM_BLOCK_TIME, [Constants.DOGE_CHAIN_NAME]: Constants.DOGE_BLOCK_TIME, [Constants.FIRO_CHAIN_NAME]: Constants.FIRO_BLOCK_TIME, + [Constants.HANDSHAKE_CHAIN_NAME]: Constants.HANDSHAKE_BLOCK_TIME, }[this.networkWatcher]; this.observationValidThreshold = Math.floor( getRequiredNumber('observation.validThreshold') / blockTime @@ -639,6 +642,37 @@ class FiroConfig { } } +class HandshakeConfig { + type: string; + initialHeight: number; + interval: number; + rpc?: { + url: string; + timeout: number; + username?: string; + password?: string; + }; + + constructor(network: string) { + this.type = config.get('handshake.type'); + if (network === Constants.HANDSHAKE_CHAIN_NAME) { + this.initialHeight = getRequiredNumber('handshake.initial.height'); + this.interval = getRequiredNumber('handshake.interval'); + if (this.type == Constants.RPC_TYPE) { + const url = getRequiredString('handshake.rpc.url'); + const timeout = getRequiredNumber('handshake.rpc.timeout'); + const username = getOptionalString('handshake.rpc.username', undefined); + const password = getOptionalString('handshake.rpc.password', undefined); + this.rpc = { url, timeout, username, password }; + } else { + throw new Error( + `Improperly configured. handshake configuration type is invalid available choices are '${Constants.RPC_TYPE}'` + ); + } + } + } +} + class DatabaseConfig { type: string; path = ''; @@ -749,6 +783,7 @@ const getConfig = (): ConfigType => { const doge = new DogeConfig(general.networkWatcher); const ethereum = new EthereumConfig(general.networkWatcher); const binance = new BinanceConfig(general.networkWatcher); + const handshake = new HandshakeConfig(general.networkWatcher); const firo = new FiroConfig(general.networkWatcher); const rosen = new RosenConfig( general.networkWatcher, @@ -764,6 +799,7 @@ const getConfig = (): ConfigType => { doge, ethereum, binance, + handshake, firo, logger, general, @@ -777,14 +813,15 @@ const getConfig = (): ConfigType => { }; export { - getConfig, - Config, - RosenConfig, - CardanoConfig, + BinanceConfig, BitcoinConfig, BitcoinRunesConfig, + CardanoConfig, + Config, + DogeConfig, EthereumConfig, - BinanceConfig, FiroConfig, - DogeConfig, + getConfig, + HandshakeConfig, + RosenConfig, }; diff --git a/src/config/constants.ts b/src/config/constants.ts index 099335de..e32e0e47 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -18,6 +18,7 @@ export const BITCOIN_RUNES_CHAIN_NAME = 'bitcoin-runes'; export const DOGE_CHAIN_NAME = 'doge'; export const ETHEREUM_CHAIN_NAME = 'ethereum'; export const BINANCE_CHAIN_NAME = 'binance'; +export const HANDSHAKE_CHAIN_NAME = 'handshake'; export const FIRO_CHAIN_NAME = 'firo'; export const ERGO_NATIVE_ASSET = 'erg'; export const ERGO_DECIMALS = 9; @@ -34,6 +35,7 @@ export const CARDANO_BLOCK_TIME = 20; export const ERGO_BLOCK_TIME = 120; export const ETHEREUM_BLOCK_TIME = 12; export const DOGE_BLOCK_TIME = 60; +export const HANDSHAKE_BLOCK_TIME = 600; export const FIRO_BLOCK_TIME = 150; export const UNISAT_TYPE = 'unisat'; export const ORDISCAN_TYPE = 'ordiscan'; diff --git a/src/jobs/initScanner.ts b/src/jobs/initScanner.ts index 0f1096d2..c8448420 100644 --- a/src/jobs/initScanner.ts +++ b/src/jobs/initScanner.ts @@ -1,9 +1,9 @@ +import { DefaultLogger } from '@rosen-bridge/abstract-logger'; import { GeneralScanner } from '@rosen-bridge/abstract-scanner'; import { CardanoOgmiosScanner } from '@rosen-bridge/cardano-scanner'; -import * as Constants from '../config/constants'; -import { getConfig } from '../config/config'; -import { DefaultLogger } from '@rosen-bridge/abstract-logger'; import { EvmRpcScanner } from '@rosen-bridge/evm-scanner'; +import { getConfig } from '../config/config'; +import * as Constants from '../config/constants'; import { CreateScanner } from '../utils/scanner'; const allConfig = getConfig(); @@ -15,6 +15,7 @@ const { doge: dogeConfig, ethereum: ethereumConfig, binance: binanceConfig, + handshake: handshakeConfig, firo: firoConfig, } = allConfig; @@ -87,6 +88,12 @@ export const scannerInit = () => { scanner.getObservationScanner() as GeneralScanner ).then(() => null); break; + case Constants.HANDSHAKE_CHAIN_NAME: + scanningJob( + handshakeConfig.interval, + scanner.getObservationScanner() as GeneralScanner + ).then(() => null); + break; case Constants.ERGO_CHAIN_NAME: break; default: diff --git a/src/types/config.ts b/src/types/config.ts index 5efedf7c..f0c94f5e 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -8,6 +8,7 @@ type NetworkType = | typeof Constants.DOGE_CHAIN_NAME | typeof Constants.ETHEREUM_CHAIN_NAME | typeof Constants.BINANCE_CHAIN_NAME - | typeof Constants.FIRO_CHAIN_NAME; + | typeof Constants.FIRO_CHAIN_NAME + | typeof Constants.HANDSHAKE_CHAIN_NAME; export { NetworkType }; diff --git a/src/utils/healthCheck.ts b/src/utils/healthCheck.ts index c9ce9b3a..29856df0 100644 --- a/src/utils/healthCheck.ts +++ b/src/utils/healthCheck.ts @@ -1,25 +1,25 @@ +import { DefaultLogger } from '@rosen-bridge/abstract-logger'; import { ErgoExplorerAssetHealthCheckParam, ErgoNodeAssetHealthCheckParam, } from '@rosen-bridge/asset-check'; +import { CardanoOgmiosScanner } from '@rosen-bridge/cardano-scanner'; +import { DiscordNotification } from '@rosen-bridge/discord-notification'; import { HealthCheck, HealthStatusLevel } from '@rosen-bridge/health-check'; +import { LogLevelHealthCheck } from '@rosen-bridge/log-level-check'; import { AbstractPermitHealthCheckParam, ExplorerPermitHealthCheckParam, NodePermitHealthCheckParam, } from '@rosen-bridge/permit-check'; import { - ScannerSyncHealthCheckParam, CardanoOgmiosScannerHealthCheck, + ScannerSyncHealthCheckParam, } from '@rosen-bridge/scanner-sync-check'; import { ExplorerWidHealthCheckParam, NodeWidHealthCheckParam, } from '@rosen-bridge/wid-check'; -import { DefaultLogger } from '@rosen-bridge/abstract-logger'; -import { CardanoOgmiosScanner } from '@rosen-bridge/cardano-scanner'; -import { DiscordNotification } from '@rosen-bridge/discord-notification'; -import { LogLevelHealthCheck } from '@rosen-bridge/log-level-check'; import { Transaction } from '../api/Transaction'; import { getConfig } from '../config/config'; @@ -33,8 +33,6 @@ import { CARDANO_CHAIN_NAME, DOGE_BLOCK_TIME, DOGE_CHAIN_NAME, - FIRO_BLOCK_TIME, - FIRO_CHAIN_NAME, ERGO_BLOCK_TIME, ERGO_CHAIN_NAME, ERGO_DECIMALS, @@ -42,6 +40,10 @@ import { ETHEREUM_BLOCK_TIME, ETHEREUM_CHAIN_NAME, EXPLORER_TYPE, + FIRO_BLOCK_TIME, + FIRO_CHAIN_NAME, + HANDSHAKE_BLOCK_TIME, + HANDSHAKE_CHAIN_NAME, NODE_TYPE, OGMIOS_TYPE, } from '../config/constants'; @@ -196,70 +198,77 @@ class HealthCheckSingleton { */ registerScannerSyncHealthCheck = () => { const scanner = CreateScanner.getInstance(); + const currentConfig = getConfig(); let scannerSyncCheck: | ScannerSyncHealthCheckParam | CardanoOgmiosScannerHealthCheck; if ( - getConfig().general.networkWatcher === CARDANO_CHAIN_NAME && - getConfig().cardano.type === OGMIOS_TYPE + currentConfig.general.networkWatcher === CARDANO_CHAIN_NAME && + currentConfig.cardano.type === OGMIOS_TYPE ) { scannerSyncCheck = new CardanoOgmiosScannerHealthCheck( this.observingNetworkLastBlock(scanner.getObservationScanner().name()), ( scanner.getObservationScanner() as CardanoOgmiosScanner ).getConnectionStatus, - getConfig().healthCheck.scannerWarnDiff, - getConfig().healthCheck.scannerCriticalDiff, + currentConfig.healthCheck.scannerWarnDiff, + currentConfig.healthCheck.scannerCriticalDiff, // TODO: Fix configuration: local/health-check/-/issues/29 - getConfig().cardano.ogmios!.connectionRetrialInterval * 15 + currentConfig.cardano.ogmios!.connectionRetrialInterval * 15 ); } else { let chainName: string; let chainBlockTime: number; let updateInterval: number; - switch (getConfig().general.networkWatcher) { + + switch (currentConfig.general.networkWatcher) { case CARDANO_CHAIN_NAME: chainName = CARDANO_CHAIN_NAME; chainBlockTime = CARDANO_BLOCK_TIME; - updateInterval = getConfig().cardano.koios!.interval; + updateInterval = currentConfig.cardano.koios!.interval; break; case BITCOIN_CHAIN_NAME: chainName = BITCOIN_CHAIN_NAME; chainBlockTime = BITCOIN_BLOCK_TIME; - updateInterval = getConfig().bitcoin.interval; + updateInterval = currentConfig.bitcoin.interval; break; case BITCOIN_RUNES_CHAIN_NAME: chainName = BITCOIN_RUNES_CHAIN_NAME; chainBlockTime = BITCOIN_BLOCK_TIME; - updateInterval = getConfig().bitcoin.interval; + updateInterval = currentConfig.bitcoin.interval; break; case DOGE_CHAIN_NAME: chainName = DOGE_CHAIN_NAME; chainBlockTime = DOGE_BLOCK_TIME; - updateInterval = getConfig().doge.interval; + updateInterval = currentConfig.doge.interval; break; case ETHEREUM_CHAIN_NAME: chainName = ETHEREUM_CHAIN_NAME; chainBlockTime = ETHEREUM_BLOCK_TIME; - updateInterval = getConfig().ethereum.interval; + updateInterval = currentConfig.ethereum.interval; break; case BINANCE_CHAIN_NAME: chainName = BINANCE_CHAIN_NAME; chainBlockTime = BINANCE_BLOCK_TIME; - updateInterval = getConfig().binance.interval; + updateInterval = currentConfig.binance.interval; break; case FIRO_CHAIN_NAME: chainName = FIRO_CHAIN_NAME; chainBlockTime = FIRO_BLOCK_TIME; - updateInterval = getConfig().firo.interval; + updateInterval = currentConfig.firo.interval; + break; + case HANDSHAKE_CHAIN_NAME: + chainName = HANDSHAKE_CHAIN_NAME; + chainBlockTime = HANDSHAKE_BLOCK_TIME; + updateInterval = currentConfig.handshake.interval; break; } scannerSyncCheck = new ScannerSyncHealthCheckParam( chainName!, this.observingNetworkLastBlock(scanner.getObservationScanner().name()), - getConfig().healthCheck.scannerWarnDiff, - getConfig().healthCheck.scannerCriticalDiff, + currentConfig.healthCheck.scannerWarnDiff, + currentConfig.healthCheck.scannerCriticalDiff, chainBlockTime!, updateInterval! ); diff --git a/src/utils/networkConnectorManagers.ts b/src/utils/networkConnectorManagers.ts index 5d18785b..729d8a0c 100644 --- a/src/utils/networkConnectorManagers.ts +++ b/src/utils/networkConnectorManagers.ts @@ -1,32 +1,36 @@ +import { DefaultLogger } from '@rosen-bridge/abstract-logger'; import { - NetworkConnectorManager, FailoverStrategy, + NetworkConnectorManager, RoundRobinStrategy, } from '@rosen-bridge/abstract-scanner'; import { - ErgoNodeNetwork, - ErgoExplorerNetwork, -} from '@rosen-bridge/ergo-scanner'; -import { - DogeRpcTransaction, - BitcoinRpcTransaction, + BitcoinEsploraTransaction, BitcoinRpcNetwork, + BitcoinRpcTransaction, DogeRpcNetwork, + DogeRpcTransaction, EsploraNetwork, - BitcoinEsploraTransaction, } from '@rosen-bridge/bitcoin-scanner'; -import { FiroRpcNetwork, FiroRpcTransaction } from '@rosen-bridge/firo-scanner'; import { - KoiosNetwork, BlockFrostNetwork, - KoiosTransaction, BlockFrostTransaction, + KoiosNetwork, + KoiosTransaction, } from '@rosen-bridge/cardano-scanner'; +import { + ErgoExplorerNetwork, + ErgoNodeNetwork, +} from '@rosen-bridge/ergo-scanner'; import { EvmRpcNetwork } from '@rosen-bridge/evm-scanner'; -import { getConfig } from '../config/config'; -import { DefaultLogger } from '@rosen-bridge/abstract-logger'; +import { FiroRpcNetwork, FiroRpcTransaction } from '@rosen-bridge/firo-scanner'; +import { + HandshakeRpcNetwork, + HandshakeRpcTransaction, +} from '@rosen-bridge/handshake-scanner'; import { Transaction } from '@rosen-bridge/scanner-interfaces'; import { TransactionResponse } from 'ethers'; +import { getConfig } from '../config/config'; const config = getConfig(); const logger = DefaultLogger.getInstance().child(import.meta.url); @@ -40,6 +44,7 @@ const cardanoKoiosLogger = logger.child('cardanoKoiosConnector'); const cardanoBlockfrostLogger = logger.child('cardanoBlockfrostConnector'); const evmLogger = logger.child('evmConnector'); const firoLogger = logger.child('firoConnector'); +const handshakeLogger = logger.child('handshakeConnector'); /** * Creates and configures a NetworkConnectorManager instance for Ergo node @@ -304,3 +309,35 @@ export const createFiroRpcNetworkConnectorManager = () => { return networkConnectorManager; }; + +/** + * Creates and configures a NetworkConnectorManager instance for Handshake RPC scanner + */ +export const createHandshakeRpcNetworkConnectorManager = () => { + const networkConnectorManager = + new NetworkConnectorManager( + new FailoverStrategy(), + handshakeLogger + ); + + if (config.handshake.rpc) { + networkConnectorManager.addConnector( + new HandshakeRpcNetwork( + config.handshake.rpc.url, + config.handshake.rpc.timeout * 1000, + config.handshake.rpc.username && config.handshake.rpc.password + ? { + username: config.handshake.rpc.username, + password: config.handshake.rpc.password, + } + : undefined + ) + ); + } else { + throw new Error( + 'Rpc configuration must be provided for Handshake Rpc network' + ); + } + + return networkConnectorManager; +}; diff --git a/src/utils/scanner.ts b/src/utils/scanner.ts index 7e2787a1..90d45231 100644 --- a/src/utils/scanner.ts +++ b/src/utils/scanner.ts @@ -1,18 +1,24 @@ +import { DefaultLogger } from '@rosen-bridge/abstract-logger'; import { ErgoUTXOExtractor } from '@rosen-bridge/address-extractor'; -import { - BitcoinEsploraScanner, - DogeEsploraScanner, - BitcoinRpcScanner, - DogeRpcScanner, -} from '@rosen-bridge/bitcoin-scanner'; import { BitcoinEsploraObservationExtractor, BitcoinRpcObservationExtractor, DogeEsploraObservationExtractor, DogeRpcObservationExtractor, } from '@rosen-bridge/bitcoin-observation-extractor'; -import { DefaultLogger } from '@rosen-bridge/abstract-logger'; -import { ErgoObservationExtractor } from '@rosen-bridge/ergo-observation-extractor'; +import { + AbstractRunesProtocolNetwork, + BitcoinRunesEsploraObservationExtractor, + BitcoinRunesRpcObservationExtractor, + OrdiscanRunesProtocolNetwork, + UnisatRunesProtocolNetwork, +} from '@rosen-bridge/bitcoin-runes-observation-extractor'; +import { + BitcoinEsploraScanner, + BitcoinRpcScanner, + DogeEsploraScanner, + DogeRpcScanner, +} from '@rosen-bridge/bitcoin-scanner'; import { CardanoBlockFrostObservationExtractor, CardanoKoiosObservationExtractor, @@ -23,7 +29,10 @@ import { CardanoKoiosScanner, CardanoOgmiosScanner, } from '@rosen-bridge/cardano-scanner'; +import { ErgoObservationExtractor } from '@rosen-bridge/ergo-observation-extractor'; import { ErgoScanner } from '@rosen-bridge/ergo-scanner'; +import { HandshakeRpcObservationExtractor } from '@rosen-bridge/handshake-observation-extractor'; +import { HandshakeRpcScanner } from '@rosen-bridge/handshake-scanner'; import { ErgoNetworkType } from '@rosen-bridge/scanner-interfaces'; import { CollateralExtractor, @@ -31,47 +40,42 @@ import { EventTriggerExtractor, PermitExtractor, } from '@rosen-bridge/watcher-data-extractor'; -import { - BitcoinRunesEsploraObservationExtractor, - BitcoinRunesRpcObservationExtractor, - AbstractRunesProtocolNetwork, - UnisatRunesProtocolNetwork, - OrdiscanRunesProtocolNetwork, -} from '@rosen-bridge/bitcoin-runes-observation-extractor'; import { BinanceRpcObservationExtractor, EthereumRpcObservationExtractor, } from '@rosen-bridge/evm-observation-extractor'; +import { EvmRpcScanner } from '@rosen-bridge/evm-scanner'; import { FiroRpcObservationExtractor } from '@rosen-bridge/firo-observation-extractor'; import { FiroRpcScanner } from '@rosen-bridge/firo-scanner'; -import { EvmRpcScanner } from '@rosen-bridge/evm-scanner'; import { dataSource } from '../../config/dataSource'; import { BinanceConfig, BitcoinConfig, + BitcoinRunesConfig, CardanoConfig, Config, + DogeConfig, EthereumConfig, FiroConfig, getConfig, + HandshakeConfig, RosenConfig, - DogeConfig, - BitcoinRunesConfig, } from '../config/config'; import * as Constants from '../config/constants'; import { TokensConfig } from '../config/tokensConfig'; import { - createCardanoKoiosNetworkConnectorManager, - createCardanoBlockfrostNetworkConnectorManager, createBitcoinEsploraNetworkConnectorManager, createBitcoinRpcNetworkConnectorManager, + createCardanoBlockfrostNetworkConnectorManager, + createCardanoKoiosNetworkConnectorManager, createDogeEsploraNetworkConnectorManager, createDogeRpcNetworkConnectorManager, + createErgoExplorerNetworkConnectorManager, + createErgoNodeNetworkConnectorManager, createEvmNetworkConnectorManager, createFiroRpcNetworkConnectorManager, - createErgoNodeNetworkConnectorManager, - createErgoExplorerNetworkConnectorManager, + createHandshakeRpcNetworkConnectorManager, } from './networkConnectorManagers'; const logger = DefaultLogger.getInstance().child(import.meta.url); @@ -106,7 +110,8 @@ class CreateScanner { | DogeEsploraScanner | DogeRpcScanner | EvmRpcScanner - | FiroRpcScanner; + | FiroRpcScanner + | HandshakeRpcScanner; private constructor() { // do nothing @@ -128,6 +133,7 @@ class CreateScanner { doge: dogeConfig, ethereum: ethereumConfig, binance: binanceConfig, + handshake: handshakeConfig, firo: firoConfig, } = allConfig; @@ -183,6 +189,13 @@ class CreateScanner { config.observationStoreRawData ); break; + case Constants.HANDSHAKE_CHAIN_NAME: + await CreateScanner.instance.createHandshakeScanner( + handshakeConfig, + rosenConfig, + config.observationStoreRawData + ); + break; } if (!CreateScanner.instance.observationScanner) throw Error( @@ -225,7 +238,8 @@ class CreateScanner { | DogeEsploraScanner | DogeRpcScanner | EvmRpcScanner - | FiroRpcScanner => { + | FiroRpcScanner + | HandshakeRpcScanner => { if (!CreateScanner.instance) { throw new Error('Scanner is not initialized'); } @@ -587,6 +601,7 @@ class CreateScanner { } } }; + private createFiroScanner = async ( firoConfig: FiroConfig, rosenConfig: RosenConfig, @@ -612,6 +627,32 @@ class CreateScanner { } } }; + + private createHandshakeScanner = async ( + handshakeConfig: HandshakeConfig, + rosenConfig: RosenConfig, + observationStoreRawData: boolean + ) => { + if (!this.observationScanner) { + if (handshakeConfig.rpc) { + this.observationScanner = new HandshakeRpcScanner({ + dataSource, + initialHeight: handshakeConfig.initialHeight, + network: createHandshakeRpcNetworkConnectorManager(), + logger: loggers.observationScannerLogger, + }); + + const observationExtractor = new HandshakeRpcObservationExtractor( + rosenConfig.lockAddress, + dataSource, + TokensConfig.getInstance().getTokenMap(), + loggers.observationExtractorLogger, + observationStoreRawData + ); + this.observationScanner.registerExtractor(observationExtractor); + } + } + }; } export { CreateScanner };