adding basic metrics

This commit is contained in:
matias martinez 2023-11-12 23:53:11 -03:00
parent 4172c62046
commit f74aea084d
17 changed files with 348 additions and 268 deletions

View File

@ -15,18 +15,15 @@ This process is meant to be deployed as a docker container. The dockerfile is lo
## Configuration
Look at the provided example .env file for configuration options.
Configuration is loaded from files in `config` directory.
There is a default file, and then a file for each environment. The environment is set by the NODE_ENV environment variable.
If NODE_ENV is not set, the default file is used.
A brief explanation of the environment variables:
RPCS : A json object of rpcs to connect to. The key is the wormhole chain ID, and the value is the rpc url. If an RPC is provided here, it is always used, regardless of the USE_DEFAULT_RPCS variable.
ENVIRONMENT : DEVNET , TESTNET, or MAINNET these are Network objects from the wormhole SDK
USE_DEFAULT_RPCS : if true, the RPCS according to the ENVIRONMENT will be used. If false, an exception is thrown if the RPCS environment variable is not set for a given chain. RPCS
Some values may be overriden by using environment variables. See `config/custom-environment-variables.json` for a list of these variables.
Contract overrides take the form of :
ETHEREUM_DEVNET_WORMHOLE_RELAYER_ADDRESS=0x53855d4b64E9A3CF59A84bc768adA716B5536BC5
BSC_DEVNET_WORMHOLE_RELAYER_ADDRESS=0x53855d4b64E9A3CF59A84bc768adA716B5536BC5
If the contract override is supplied, it will be used, otherwise the default contract addresses from the wormhole SDK will be used instead.
```bash
$ NODE_ENV=staging LOG_LEVEL=debug npm run dev
```
## Usage & Modification

View File

@ -1,5 +1,6 @@
{
"environment": "BLOCKCHAIN_ENV",
"port": "PORT",
"logLevel": "LOG_LEVEL",
"dryRun": "DRY_RUN_ENABLED",
"sns": {

View File

@ -1,5 +1,6 @@
{
"environment": "testnet",
"port": 9090,
"logLevel": "debug",
"dryRun": true,
"supportedChains": ["ethereum"],

View File

@ -17,6 +17,7 @@
"config": "^3.3.9",
"dotenv": "^16.3.1",
"ethers": "^5",
"prom-client": "^15.0.0",
"uuid": "^9.0.1",
"winston": "3.8.2"
},
@ -3368,33 +3369,6 @@
"node": ">=10"
}
},
"node_modules/@jest/reporters/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@jest/reporters/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@jest/reporters/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -3407,12 +3381,6 @@
"node": ">=8"
}
},
"node_modules/@jest/reporters/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@jest/schemas": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
@ -3829,6 +3797,14 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz",
"integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@project-serum/anchor": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.25.0.tgz",
@ -5603,6 +5579,11 @@
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bintrees": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
},
"node_modules/bip32": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
@ -8921,33 +8902,6 @@
"node": ">=8"
}
},
"node_modules/jest-snapshot/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jest-snapshot/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jest-snapshot/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -8960,12 +8914,6 @@
"node": ">=8"
}
},
"node_modules/jest-snapshot/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
@ -9577,39 +9525,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/make-dir/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@ -10173,6 +10088,18 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"optional": true
},
"node_modules/prom-client": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.0.0.tgz",
"integrity": "sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA==",
"dependencies": {
"@opentelemetry/api": "^1.4.0",
"tdigest": "^0.1.1"
},
"engines": {
"node": "^16 || ^18 || >=20"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@ -10522,6 +10449,39 @@
"node": ">=10.0.0"
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@ -10862,6 +10822,14 @@
"node": ">=0.10"
}
},
"node_modules/tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
"dependencies": {
"bintrees": "1.0.2"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@ -11074,39 +11042,6 @@
}
}
},
"node_modules/ts-jest/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-jest/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-jest/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/ts-jest/node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
@ -14094,24 +14029,6 @@
"semver": "^7.5.4"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -14120,12 +14037,6 @@
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
@ -14471,6 +14382,11 @@
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA=="
},
"@opentelemetry/api": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz",
"integrity": "sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw=="
},
"@project-serum/anchor": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.25.0.tgz",
@ -15960,6 +15876,11 @@
"file-uri-to-path": "1.0.0"
}
},
"bintrees": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="
},
"bip32": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
@ -18478,24 +18399,6 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -18504,12 +18407,6 @@
"requires": {
"has-flag": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
@ -18982,32 +18879,6 @@
"dev": true,
"requires": {
"semver": "^7.5.3"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"make-error": {
@ -19446,6 +19317,15 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"optional": true
},
"prom-client": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.0.0.tgz",
"integrity": "sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA==",
"requires": {
"@opentelemetry/api": "^1.4.0",
"tdigest": "^0.1.1"
}
},
"prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@ -19693,6 +19573,32 @@
"node-gyp-build": "^4.2.0"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@ -19953,6 +19859,14 @@
"integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
"optional": true
},
"tdigest": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
"requires": {
"bintrees": "1.0.2"
}
},
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@ -20120,30 +20034,6 @@
"yargs-parser": "^21.0.1"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",

View File

@ -22,6 +22,7 @@
"config": "^3.3.9",
"dotenv": "^16.3.1",
"ethers": "^5",
"prom-client": "^15.0.0",
"uuid": "^9.0.1",
"winston": "3.8.2"
},

View File

@ -1,5 +1,5 @@
import { EvmLog } from "../entities";
import { EvmBlockRepository, MetadataRepository } from "../repositories";
import { EvmBlockRepository, MetadataRepository, StatRepository } from "../repositories";
import { setTimeout } from "timers/promises";
import winston from "winston";
@ -10,21 +10,26 @@ let ref: any;
* PollEvmLogs is an action that watches for new blocks and extracts logs from them.
*/
export class PollEvmLogs {
private readonly logger: winston.Logger = winston.child({ module: "PollEvmLogs" });
private readonly blockRepo: EvmBlockRepository;
private readonly metadataRepo: MetadataRepository<PollEvmLogsMetadata>;
private readonly statsRepository: StatRepository;
private cfg: PollEvmLogsConfig;
private latestBlockHeight?: bigint;
private blockHeightCursor?: bigint;
private cfg: PollEvmLogsConfig;
private started: boolean = false;
private readonly logger: winston.Logger = winston.child({ module: "PollEvmLogs" });
constructor(
blockRepo: EvmBlockRepository,
metadataRepo: MetadataRepository<PollEvmLogsMetadata>,
statsRepository: StatRepository,
cfg: PollEvmLogsConfig
) {
this.blockRepo = blockRepo;
this.metadataRepo = metadataRepo;
this.statsRepository = statsRepository;
this.cfg = cfg;
}
@ -40,6 +45,7 @@ export class PollEvmLogs {
private async watch(handlers: ((logs: EvmLog[]) => Promise<void>)[]): Promise<void> {
while (this.started) {
this.report();
if (this.cfg.hasFinished(this.blockHeightCursor)) {
this.logger.info(
`PollEvmLogs: (${this.cfg.id}) Finished processing all blocks from ${this.cfg.fromBlock} to ${this.cfg.toBlock}`
@ -115,13 +121,21 @@ export class PollEvmLogs {
return { fromBlock, toBlock };
}
private report(): void {
const labels = {
job: this.cfg.id,
chain: this.cfg.chain ?? "",
commitment: this.cfg.getCommitment(),
};
this.statsRepository.count("job_execution", labels);
this.statsRepository.measure("block_height", this.latestBlockHeight ?? 0n, labels);
this.statsRepository.measure("block_cursor", this.blockHeightCursor ?? 0n, labels);
}
public async stop(): Promise<void> {
clearTimeout(ref);
this.started = false;
}
// TODO: schedule getting latest block height in chain or use the value from poll to keep metrics updated
// this.latestBlockHeight = await this.blockRepo.getBlockHeight(this.commitment);
}
export type PollEvmLogsMetadata = {
@ -137,6 +151,7 @@ export interface PollEvmLogsConfigProps {
addresses: string[];
topics: string[];
id?: string;
chain?: string;
}
export class PollEvmLogsConfig {
@ -190,6 +205,10 @@ export class PollEvmLogsConfig {
return this.props.id ?? ID;
}
public get chain() {
return this.props.chain;
}
static fromBlock(fromBlock: bigint) {
const cfg = new PollEvmLogsConfig();
cfg.props.fromBlock = fromBlock;

View File

@ -10,3 +10,9 @@ export interface MetadataRepository<Metadata> {
get(id: string): Promise<Metadata | undefined>;
save(id: string, metadata: Metadata): Promise<void>;
}
export interface StatRepository {
count(id: string, labels: Record<string, any>): void;
measure(id: string, value: bigint, labels: Record<string, any>): void;
report: () => Promise<string>;
}

View File

@ -5,6 +5,7 @@ import {
EvmJsonRPCBlockRepository,
EvmJsonRPCBlockRepositoryCfg,
FileMetadataRepo,
PromStatRepository,
} from "./repositories";
import axios, { AxiosInstance } from "axios";
import axiosRateLimit from "axios-rate-limit";
@ -25,6 +26,7 @@ export class RepositoriesBuilder {
this.axiosInstance = this.createAxios();
this.repositories.set("sns", new SnsEventRepository(this.snsClient, this.cfg.sns));
this.repositories.set("metrics", new PromStatRepository());
this.cfg.metadata?.dir &&
this.repositories.set("metadata", new FileMetadataRepo(this.cfg.metadata.dir));
@ -63,6 +65,13 @@ export class RepositoriesBuilder {
return repo;
}
public getStatsRepository(): PromStatRepository {
const repo = this.repositories.get("metrics");
if (!repo) throw new Error(`No PromStatRepository`);
return repo;
}
public close(): void {
this.snsClient?.destroy();
}

View File

@ -3,6 +3,7 @@ import { SnsConfig } from "./repositories/SnsEventRepository";
export type Config = {
environment: "testnet" | "mainnet";
port: number;
logLevel: "debug" | "info" | "warn" | "error";
dryRun: boolean;
sns: SnsConfig;
@ -28,6 +29,7 @@ export type PlatformConfig = {
*/
export const configuration = {
environment: config.get<string>("environment"),
port: config.get<number>("port") ?? 9090,
logLevel: config.get<string>("logLevel")?.toLowerCase() ?? "info",
dryRun: config.get<string>("dryRun") === "true" ? true : false,
sns: config.get<SnsConfig>("sns"),

View File

@ -1,6 +1,5 @@
import { ChainId, ChainName, Network, toChainName } from "@certusone/wormhole-sdk";
import AbstractWatcher from "./watchers/AbstractWatcher";
import { rootLogger } from "./log";
import winston from "winston";
import EvmWatcher from "./watchers/EvmWatcher";
import AbstractHandler from "./handlers/AbstractHandler";
@ -126,7 +125,7 @@ export async function initializeEnvironment(configurationPath: string) {
configuration,
supportedChains,
rpcs,
logger: rootLogger,
logger: winston.child({}),
};
}

View File

@ -0,0 +1,55 @@
import prometheus from "prom-client";
import { StatRepository } from "../../domain/repositories";
export class PromStatRepository implements StatRepository {
private readonly registry: prometheus.Registry;
private counters: Map<string, prometheus.Counter<string>> = new Map();
private gauges: Map<string, prometheus.Gauge<string>> = new Map();
constructor(registry?: prometheus.Registry) {
this.registry = registry ?? new prometheus.Registry();
}
public report() {
return this.registry.metrics();
}
public count(id: string, labels: Record<string, any>): void {
const counter = this.getCounter(id, labels);
counter.inc(labels);
}
public measure(id: string, value: bigint, labels: Record<string, any>): void {
const gauge = this.getGauge(id, labels);
gauge.set(labels, Number(value));
}
private getCounter(id: string, labels: Record<string, any>): prometheus.Counter {
this.counters.get(id) ??
this.counters.set(
id,
new prometheus.Counter({
name: id,
help: id,
registers: [this.registry],
labelNames: Object.keys(labels),
})
);
return this.counters.get(id) as prometheus.Counter<string>;
}
private getGauge(id: string, labels: Record<string, any>): prometheus.Gauge {
this.gauges.get(id) ??
this.gauges.set(
id,
new prometheus.Gauge({
name: id,
help: id,
registers: [this.registry],
labelNames: Object.keys(labels),
})
);
return this.gauges.get(id) as prometheus.Gauge;
}
}

View File

@ -10,3 +10,4 @@ if (!("toJSON" in BigInt.prototype)) {
export * from "./FileMetadataRepo";
export * from "./SnsEventRepository";
export * from "./EvmJsonRPCBlockRepository";
export * from "./PromStatRepository";

View File

@ -0,0 +1,13 @@
import { StatRepository } from "../../domain/repositories";
export class HealthController {
private readonly statsRepo: StatRepository;
constructor(statsRepo: StatRepository) {
this.statsRepo = statsRepo;
}
metrics = async () => {
return this.statsRepo.report();
};
}

View File

@ -0,0 +1,40 @@
import http from "http";
import url from "url";
import { HealthController } from "./HealthController";
import log from "../log";
export class WebServer {
private server: http.Server;
private port: number;
constructor(port: number, healthController: HealthController) {
this.port = port;
this.server = http.createServer(async (req, res) => {
const route = url.parse(req.url ?? "").pathname;
if (route === "/metrics") {
// Return all metrics the Prometheus exposition format
res.setHeader("Content-Type", "text/plain");
res.end(await healthController.metrics());
}
if (route === "/health") {
res.end("OK");
}
res.statusCode = 404;
res.end();
});
this.start();
}
start() {
this.server.listen(this.port, () => {
log.info(`Server started on port 8080`);
});
}
stop() {
this.server.close();
}
}

View File

@ -4,14 +4,37 @@ import { configuration } from "./infrastructure/config";
import { evmLogMessagePublishedMapper } from "./infrastructure/mappers/evmLogMessagePublishedMapper";
import { RepositoriesBuilder } from "./infrastructure/RepositoriesBuilder";
import log from "./infrastructure/log";
import { WebServer } from "./infrastructure/rpc/Server";
import { HealthController } from "./infrastructure/rpc/HealthController";
let repos: RepositoriesBuilder;
let server: WebServer;
async function run(): Promise<void> {
log.info(`Starting: dryRunEnabled -> ${configuration.dryRun}`);
repos = new RepositoriesBuilder(configuration);
await startServer(repos);
await startJobs(repos);
// Just keep this running until killed
setInterval(() => {
log.info("Still running");
}, 20_000);
log.info("Started");
// Handle shutdown
process.on("SIGINT", handleShutdown);
process.on("SIGTERM", handleShutdown);
}
const startServer = async (repos: RepositoriesBuilder) => {
server = new WebServer(configuration.port, new HealthController(repos.getStatsRepository()));
};
const startJobs = async (repos: RepositoriesBuilder) => {
/** Job definition is hardcoded, but should be loaded from cfg or a data store soon enough */
const jobs = [
{
@ -21,11 +44,11 @@ async function run(): Promise<void> {
action: "PollEvmLogs",
config: {
fromBlock: 10012499n,
// toBlock: 10012999n,
blockBatchSize: 100,
commitment: "latest",
interval: 15_000,
addresses: ["0x706abc4E45D419950511e474C7B9Ed348A4a716c"],
chain: "ethereum",
topics: [],
},
},
@ -49,6 +72,7 @@ async function run(): Promise<void> {
const pollEvmLogs = new PollEvmLogs(
repos.getEvmBlockRepository("ethereum"),
repos.getMetadataRepository(),
repos.getStatsRepository(),
new PollEvmLogsConfig({ ...jobs[0].source.config, id: jobs[0].id })
);
@ -72,25 +96,11 @@ async function run(): Promise<void> {
);
pollEvmLogs.start([handleEvmLogs.handle.bind(handleEvmLogs)]);
// Just keep this running until killed
setInterval(() => {
log.info("Still running");
}, 20_000);
log.info("Started");
// Handle shutdown
process.on("SIGINT", handleShutdown);
process.on("SIGTERM", handleShutdown);
}
};
const handleShutdown = async () => {
try {
await Promise.allSettled([
repos.close(),
// call stop() on all the things
]);
await Promise.allSettled([repos.close(), server.stop()]);
process.exit();
} catch (error: unknown) {

View File

@ -5,7 +5,11 @@ import {
PollEvmLogs,
PollEvmLogsConfig,
} from "../../src/domain/actions/PollEvmLogs";
import { EvmBlockRepository, MetadataRepository } from "../../src/domain/repositories";
import {
EvmBlockRepository,
MetadataRepository,
StatRepository,
} from "../../src/domain/repositories";
import { EvmBlock, EvmLog } from "../../src/domain/entities";
let cfg = PollEvmLogsConfig.fromBlock(0n);
@ -17,6 +21,8 @@ let metadataSaveSpy: jest.SpiedFunction<MetadataRepository<PollEvmLogsMetadata>[
let metadataRepo: MetadataRepository<PollEvmLogsMetadata>;
let evmBlockRepo: EvmBlockRepository;
let statsRepo: StatRepository;
let handlers = {
working: (logs: EvmLog[]) => Promise.resolve(),
failing: (logs: EvmLog[]) => Promise.reject(),
@ -33,6 +39,7 @@ describe("PollEvmLogs", () => {
const blocksAhead = 1n;
givenEvmBlockRepository(currentHeight, blocksAhead);
givenMetadataRepository();
givenStatsRepository();
givenPollEvmLogs();
await whenPollEvmLogsStarts();
@ -55,6 +62,7 @@ describe("PollEvmLogs", () => {
const blocksAhead = 10n;
givenEvmBlockRepository(lastExtractedBlock, blocksAhead);
givenMetadataRepository({ lastBlock: lastExtractedBlock });
givenStatsRepository();
givenPollEvmLogs(lastExtractedBlock - 10n);
await whenPollEvmLogsStarts();
@ -79,6 +87,7 @@ describe("PollEvmLogs", () => {
const blocksAhead = 1n;
givenEvmBlockRepository(currentHeight, blocksAhead);
givenMetadataRepository();
givenStatsRepository();
givenPollEvmLogs(currentHeight);
await whenPollEvmLogsStarts();
@ -137,9 +146,17 @@ const givenMetadataRepository = (data?: PollEvmLogsMetadata) => {
metadataSaveSpy = jest.spyOn(metadataRepo, "save");
};
const givenStatsRepository = () => {
statsRepo = {
count: () => {},
measure: () => {},
report: () => Promise.resolve(""),
};
};
const givenPollEvmLogs = (from?: bigint) => {
cfg.setFromBlock(from);
pollEvmLogs = new PollEvmLogs(evmBlockRepo, metadataRepo, cfg);
pollEvmLogs = new PollEvmLogs(evmBlockRepo, metadataRepo, statsRepo, cfg);
};
const whenPollEvmLogsStarts = async () => {

View File

@ -1,3 +1,20 @@
---
apiVersion: v1
kind: Service
metadata:
name: {{ .NAME }}
namespace: {{ .NAMESPACE }}
labels:
app: {{ .NAME }}
spec:
selector:
app: {{ .NAME }}
ports:
- port: {{ .PORT }}
targetPort: {{ .PORT }}
name: {{ .NAME }}
protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
@ -13,6 +30,9 @@ spec:
metadata:
labels:
app: {{ .NAME }}
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "{{ .PORT }}"
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 30
@ -49,4 +69,3 @@ spec:
- name: metadata-volume
persistentVolumeClaim:
claimName: blockchain-watcher-metadata-pvc