Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
43f8e2f
build(deps): bump semver from 7.3.8 to 7.5.4
dependabot[bot] Jul 14, 2023
98103c7
build(deps): bump word-wrap from 1.2.3 to 1.2.4
dependabot[bot] Jul 20, 2023
f1c13af
feat: initial refactoring
eddort Mar 6, 2025
dd3e8be
feat: refactor config to zod
eddort Mar 6, 2025
d920dca
feat: refactor dto
eddort Mar 6, 2025
af34230
fix: ts-node dev mode
eddort Mar 6, 2025
8e1f13a
feat: add support for multiple operator identifiers in configuration
eddort Mar 6, 2025
4e0d5cc
fix: change chain_id type from number to string in depositContractSchema
eddort Mar 6, 2025
d42b438
fix: ensure remoteChainId is compared as a number in print-capella-fo…
eddort Mar 6, 2025
29dc0f5
Merge pull request #92 from lidofinance/dependabot/npm_and_yarn/word-…
eddort Mar 6, 2025
35a2fec
Merge pull request #91 from lidofinance/dependabot/npm_and_yarn/semve…
eddort Mar 6, 2025
4c462d3
fix: set package as private and upgrade vite version to 6.2.0
eddort Mar 6, 2025
11f1d38
fix: upgrade vitest version to 3.0.7 in package.json
eddort Mar 6, 2025
61a8a65
Merge pull request #125 from lidofinance/main
eddort Mar 24, 2025
86db625
refactor: rollback zod changes
eddort Mar 24, 2025
7290665
Merge branch 'develop' of github.com:lidofinance/validator-ejector in…
eddort Mar 24, 2025
5022201
refactor: update imports to use local lib instead nanolib
eddort Mar 24, 2025
327b992
refactor: eslint fix
eddort Mar 24, 2025
7cbab24
fix: correct date formatting to use UTC in logger
eddort Mar 24, 2025
1662670
feat: implement exit logs service with fetcher and DTOs
eddort Mar 24, 2025
d7a3be4
feat: integrate exit logs service into job processor and application …
eddort Mar 24, 2025
df3ec06
fix: update debug log color to blue in logger printer
eddort Mar 25, 2025
8a115e8
feat: implement LRUCache class with basic operations and tests
eddort Mar 25, 2025
eb1d3a6
feat: implement exit logs caching and fetching service with types
eddort Mar 25, 2025
6090312
feat: refactor exit logs service and add logs fetching method in exec…
eddort Mar 25, 2025
e1caed4
feat: simplify exit logs service initialization and enhance JSON resp…
eddort Mar 25, 2025
df4168d
feat: enhance exit logs processing by adding node operator ID and ack…
eddort Mar 25, 2025
d515b7c
feat: enhance configuration validation for OPERATOR_ID and OPERATOR_I…
eddort Mar 25, 2025
f23d920
feat: update exit logs fetching to include last block number and impr…
eddort Mar 25, 2025
e18c0f7
feat: rename pooling method to loop and update job execution logic
eddort Mar 25, 2025
7a51d3d
feat: update job processing logic to acknowledge events based on fina…
eddort Mar 25, 2025
3a9c084
feat: add base end-to-end tests for exit logs service and increase te…
eddort Mar 25, 2025
893db2d
fix: correct typo in README for polling_last_blocks_duration_seconds …
eddort Mar 25, 2025
2970441
feat: enhance error handling by adding safelyParseJsonResponse for CL…
eddort Mar 26, 2025
037daec
feat: enable exitLogs e2e tests and enhance test timeout for improved…
eddort Mar 26, 2025
bc3bf63
fix: update public execution node URL
eddort Mar 26, 2025
182a073
test: add validation for operator identification in config module
eddort Mar 26, 2025
3f06c9d
fix: reduce LRU cache size for transaction and consensus logs to opti…
eddort Mar 26, 2025
2ef1409
fix: remove lido-nanolib dependency
eddort Mar 26, 2025
cb32e8e
feat: enhance logging with heap size limit and fetch time metrics in …
eddort Mar 26, 2025
b882b40
test: add unit tests for ExitLogsService to validate log fetching beh…
eddort Mar 26, 2025
d758912
fix: update nock dependency version to remove caret for consistency
eddort Mar 27, 2025
1b2e4d6
test: implement unit tests for JSON and simple output formats in logger
eddort Mar 31, 2025
9fb54a5
fix: update exitLogs tests to use environment variables for node conf…
eddort Mar 31, 2025
a82c731
fix: increase test timeout to 10 minutes for improved test execution
eddort Mar 31, 2025
e3933ed
fix: correct debug color comment and enhance error logging for JSON r…
eddort Mar 31, 2025
f0e1c49
fix: remove unused getLastFromCache method from exit logs cache service
eddort Mar 31, 2025
1bbadbb
fix: add comment to clarify the number of blocks for ConsensusReached…
eddort Mar 31, 2025
cc50332
fix: remove BLOCKS_LOOP configuration option from README and service
eddort Mar 31, 2025
1aa88db
fix: unify operator identification handling by replacing OPERATOR_IDE…
eddort Apr 1, 2025
7853abb
fix: enhance error logging for transaction report hash lookup and dat…
eddort Apr 1, 2025
abeffc1
fix: update exit logs tests to use hardcoded mainnet block numbers wi…
eddort Apr 1, 2025
3bff5b6
fix: replace LRUCache implementation with lru-cache package and updat…
eddort Apr 1, 2025
b33856e
fix: rename logs function to getLogs for clarity and consistency
eddort Apr 1, 2025
e0715a0
fix: update exit logs cache header initialization and adjust related …
eddort Apr 1, 2025
96fbf0b
fix: simplify cached logs check by removing redundant condition
eddort Apr 1, 2025
b1c7c71
fix: add comment for future use of baseUrl in the balancer mechanism
eddort Apr 2, 2025
73f6973
add nethermind rpc authorization support
2019jack Apr 4, 2025
2f9fa05
improve JWT authentication implementation
2019jack Apr 5, 2025
e9a61b7
fix: update JWT implementation to generate new token per request
2019jack Apr 5, 2025
59da8d3
Merge pull request #127 from lidofinance/feat/logs-service
eddort Apr 25, 2025
7c84853
chore: format code for consistency
eddort Apr 25, 2025
606979f
test: add unit tests for JWT service initialization and token generation
eddort Apr 25, 2025
4b533fc
Merge branch 'feat/local-lib' of github.com:lidofinance/validator-eje…
eddort Apr 25, 2025
324bde7
refactor: streamline request header handling in execution API and ver…
eddort Apr 25, 2025
2c48231
Merge pull request #131 from lidofinance/pr-128
eddort Apr 28, 2025
7b2a14c
docs: add OPERATOR_IDENTIFIERS option to README for multiple operator…
eddort Apr 28, 2025
0422765
Merge pull request #126 from lidofinance/feat/local-lib
eddort Apr 29, 2025
00f1129
chore: upgrade Node.js version to 22-alpine and update dependencies
eddort Apr 29, 2025
9d2208a
test: skip messages processor tests for now
eddort Apr 29, 2025
64d0de9
chore: rollback vite and vitest
eddort Apr 29, 2025
f4421f7
test: enable messages processor tests
eddort Apr 29, 2025
f793d8d
fix: rollback dockerfile changes
eddort Apr 29, 2025
f3ec649
Merge pull request #133 from lidofinance/feat/update-deps
eddort Apr 29, 2025
7f4223e
Merge pull request #135 from lidofinance/main
eddort Apr 30, 2025
04f963b
chore: bump version to 1.7.1
eddort Apr 30, 2025
d0e7496
Merge pull request #136 from lidofinance/feat/bump-pre-release
eddort Apr 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ node_modules
/messages
dist
.jest

jwt.hex
/.idea
artifacts
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ Options are configured via environment variables.
|--------------------------------| -------- | --------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| EXECUTION_NODE | Yes | http://1.2.3.4:8545 | Ethereum Execution Node endpoint |
| CONSENSUS_NODE | Yes | http://1.2.3.4:5051 | Ethereum Consensus Node endpoint |
| JWT_SECRET_PATH | No | /data/neth/jwt.hex | Path to JWT secret hex file for Nethermind RPC authentication. If provided, will generate a new JWT token for each execution node request |
| LOCATOR_ADDRESS | Yes | 0x123 | Address of the Locator contract [Goerli](https://docs.lido.fi/deployed-contracts/goerli/) / [Mainnet](https://docs.lido.fi/deployed-contracts/) |
| STAKING_MODULE_ID | Yes | 123 | Staking Module ID for which operator ID is set, currently only one exists - ([NodeOperatorsRegistry](https://github.com/lidofinance/lido-dao#contracts)) with id `1` |
| OPERATOR_ID | Yes | 123 | Operator ID in the Node Operators registry, easiest to get from Operators UI: [Goerli](https://operators.testnet.fi)/[Mainnet](https://operators.lido.fi) |
| OPERATOR_IDENTIFIERS | No | [0,1,2] | Alternative to OPERATOR_ID. Array of Operator IDs to handle exits for multiple operators simultaneously |
| MESSAGES_LOCATION | No | messages | Local folder or external storage bucket url to load json exit message files from. Required if you are using exit messages mode |
| VALIDATOR_EXIT_WEBHOOK | No | http://webhook | POST validator info to an endpoint instead of sending out an exit message in order to initiate an exit. Required if you are using webhook mode |
| ORACLE_ADDRESSES_ALLOWLIST | Yes | ["0x123"] | Allowed Oracle addresses to accept transactions from [Goerli](https://testnet.testnet.fi/#/lido-testnet-prater/0x24d8451bc07e7af4ba94f69acdd9ad3c6579d9fb/) / [Mainnet](https://mainnet.lido.fi/#/lido-dao/0x442af784a788a5bd6f42a01ebe9f287a871243fb/) |
| MESSAGES_PASSWORD | No | password | Password to decrypt encrypted exit messages with. Needed only if you encrypt your exit messages |
| MESSAGES_PASSWORD_FILE | No | password_inside.txt | Path to a file with password inside to decrypt exit messages with. Needed only if you have encrypted exit messages. If used, MESSAGES_PASSWORD (not MESSAGES_PASSWORD_FILE) needs to be added to LOGGER_SECRETS in order to be sanitized |
| BLOCKS_PRELOAD | No | 50000 | Amount of blocks to load events from on start. Increase if daemon was not running for some time. Defaults to a week of blocks |
| BLOCKS_LOOP | No | 900 | Amount of blocks to load events from on every poll. Defaults to 3 hours of blocks |
| JOB_INTERVAL | No | 384000 | Time interval in milliseconds to run checks. Defaults to time of 1 epoch |
| HTTP_PORT | No | 8989 | Port to serve metrics and health check on |
| RUN_METRICS | No | false | Enable metrics endpoint |
Expand Down Expand Up @@ -129,7 +130,7 @@ Available metrics:
- exit_messages: ['valid'] - Exit messages and their validity: JSON parseability, structure and signature
- exit_actions: ['result'] - Statuses of initiated validator exits
- event_security_verification: ['result'] - Statuses of exit event security verifications
- polling_last_blocks_duration_seconds: ['eventsNumber'] - Duration of pooling last blocks in microseconds
- polling_last_blocks_duration_seconds: ['eventsNumber'] - Duration of polling last blocks in microseconds
- execution_request_duration_seconds: ['result', 'status', 'domain'] - Execution node request duration in microseconds
- consensus_request_duration_seconds: ['result', 'status', 'domain'] - Consensus node request duration in microseconds
- job_duration_seconds: ['name', 'interval', 'result'] - Duration of Ejector cycle cron job
Expand Down Expand Up @@ -168,3 +169,4 @@ ln -s /opt/homebrew/bin/python3 /usr/local/bin/python
```

More info here - https://github.com/ChainSafe/lodestar/issues/4767#issuecomment-1640631566

18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "validator-ejector",
"version": "1.7.0",
"version": "1.7.1",
"private": true,
"main": "index.js",
"license": "MIT",
"dependencies": {
Expand All @@ -16,14 +17,15 @@
"@lodestar/utils": "1.2.2",
"dotenv": "16.0.3",
"ethers": "5.7.2",
"lido-nanolib": "1.4.0",
"jsonwebtoken": "9.0.0",
"lru-cache": "10.4.3",
"node-fetch": "3.3.0",
"prom-client": "14.1.0",
"typescript": "4.9.3"
},
"type": "module",
"scripts": {
"dev": "ts-node src/index.ts",
"dev": "node --loader ts-node/esm --disable-warning=ExperimentalWarning src/index.ts",
"test": "vitest",
"coverage": "vitest run --coverage",
"lint": "eslint --ext ts .",
Expand All @@ -32,17 +34,18 @@
"encrypt": "ts-node encryptor/encrypt.ts"
},
"devDependencies": {
"@types/jsonwebtoken": "9.0.1",
"@types/node": "18.11.11",
"@typescript-eslint/eslint-plugin": "5.45.1",
"@typescript-eslint/parser": "5.45.1",
"eslint": "8.29.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1",
"nock": "^13.3.1",
"nock": "13.3.1",
"prettier": "2.8.0",
"ts-node": "10.9.1",
"vite": "^4.3.3",
"vitest": "^0.30.1"
"vite": "4.5.13",
"vitest": "0.30.1"
},
"jest": {
"extensionsToTreatAsEsm": [
Expand All @@ -63,5 +66,6 @@
"dotenv/config"
],
"testEnvironment": "node"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
1 change: 1 addition & 0 deletions sample.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
EXECUTION_NODE=http://1.2.3.4:8545
JWT_SECRET_PATH=/data/neth/jwt.hex
CONSENSUS_NODE=http://1.2.3.4:5051
LOCATOR_ADDRESS=0x1eDf09b5023DC86737b59dE68a8130De878984f5
STAKING_MODULE_ID=1
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeLogger } from 'lido-nanolib'
import { makeLogger } from '../lib/index.js'
import nock from 'nock'
import { makeConfig as mC } from '../services/config/service.js'
import { mockEthServer } from '../test/mock-eth-server.js'
Expand Down
2 changes: 1 addition & 1 deletion src/app/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JobRunnerService, LoggerService } from 'lido-nanolib'
import type { JobRunnerService, LoggerService } from '../lib/index.js'
import type { ConfigService } from '../services/config/service.js'
import type { MetricsService } from '../services/prom/service.js'
import type { HttpHandlerService } from '../services/http-handler/service.js'
Expand Down
39 changes: 27 additions & 12 deletions src/app/module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { makeLogger } from 'lido-nanolib'
import { makeRequest } from 'lido-nanolib'
import { makeLogger } from '../lib/index.js'
import { makeRequest } from '../lib/index.js'
import {
logger as loggerMiddleware,
notOkError,
retry,
abort,
prom,
} from 'lido-nanolib'
import { makeJobRunner } from 'lido-nanolib'
} from '../lib/index.js'
import { makeJobRunner } from '../lib/index.js'

import dotenv from 'dotenv'

Expand All @@ -18,6 +18,7 @@ import {
} from '../services/config/service.js'
import { makeConsensusApi } from '../services/consensus-api/service.js'
import { makeExecutionApi } from '../services/execution-api/service.js'
import { makeJwtService } from '../services/jwt/service.js'
import { makeMetrics, register } from '../services/prom/service.js'
import { makeLocalFileReader } from '../services/local-file-reader/service.js'
import { makeMessagesProcessor } from '../services/messages-processor/service.js'
Expand All @@ -30,6 +31,7 @@ import { makeGsStore } from '../services/gs-store/service.js'
import { makeApp } from './service.js'
import { makeMessageReloader } from '../services/message-reloader/message-reloader.js'
import { makeForkVersionResolver } from '../services/fork-version-resolver/service.js'
import { makeExitLogsService } from '../services/exit-logs/service.js'

dotenv.config()

Expand All @@ -49,19 +51,31 @@ export const makeAppModule = async () => {

const metrics = makeMetrics({ PREFIX: config.PROM_PREFIX })

const jwtService = makeJwtService(logger, {
JWT_SECRET_PATH: config.JWT_SECRET_PATH || '',
})

if (config.JWT_SECRET_PATH) {
await jwtService.initialize()
}

const executionHttp = makeRequest([
retry(3),
loggerMiddleware(logger),
prom(metrics.executionRequestDurationSeconds),
notOkError(),
abort(30_000),
])

const executionApi = makeExecutionApi(
makeRequest([
retry(3),
loggerMiddleware(logger),
prom(metrics.executionRequestDurationSeconds),
notOkError(),
abort(30_000),
]),
executionHttp,
logger,
config,
metrics
jwtService
)

const exitLogs = makeExitLogsService(logger, executionApi, config, metrics)

const consensusApi = makeConsensusApi(
makeRequest([
retry(3),
Expand Down Expand Up @@ -110,6 +124,7 @@ export const makeAppModule = async () => {
config,
messageReloader,
executionApi,
exitLogs,
consensusApi,
messagesProcessor,
webhookProcessor,
Expand Down
33 changes: 22 additions & 11 deletions src/app/service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Dependencies } from './interface.js'
import { MessageStorage } from '../services/job-processor/message-storage.js'
import { getHeapStatistics } from 'v8'

export const makeApp = ({
config,
Expand All @@ -11,14 +12,22 @@ export const makeApp = ({
consensusApi,
appInfoReader,
}: Dependencies) => {
const { OPERATOR_ID, BLOCKS_PRELOAD, BLOCKS_LOOP, JOB_INTERVAL } = config
const { OPERATOR_ID, BLOCKS_PRELOAD, JOB_INTERVAL, OPERATOR_IDENTIFIERS } =
config

let ejectorCycleTimer: NodeJS.Timer | null = null

const run = async () => {
const version = await appInfoReader.getVersion()
const mode = config.MESSAGES_LOCATION ? 'message' : 'webhook'
logger.info(`Validator Ejector v${version} started in ${mode} mode`, config)

const { heap_size_limit } = getHeapStatistics()
const heapLimit = Math.round(heap_size_limit / 1024 / 1024).toString()

logger.info(`Validator Ejector v${version} started in ${mode} mode`, {
...config,
heapLimit,
})

metrics.buildInfo
.labels({
Expand All @@ -35,23 +44,25 @@ export const makeApp = ({
const messageStorage = new MessageStorage()

logger.info(
`Starting, searching only for requests for operator ${OPERATOR_ID}`
`Starting, searching only for requests for operators ${
OPERATOR_ID ?? OPERATOR_IDENTIFIERS
}`
)

logger.info(`Loading initial events for ${BLOCKS_PRELOAD} last blocks`)
const fetchTimeStart = performance.now()

await job.once({
eventsNumber: BLOCKS_PRELOAD,
messageStorage: messageStorage,
})

logger.info(
`Starting ${
JOB_INTERVAL / 1000
} seconds polling for ${BLOCKS_LOOP} last blocks`
)
const fetchTimeEnd = performance.now()
const fetchTime = Math.ceil(fetchTimeEnd - fetchTimeStart) / 1000
logger.info(`Initial events loaded in ${fetchTime} seconds`)

logger.info(`Starting ${JOB_INTERVAL / 1000} seconds polling`)

ejectorCycleTimer = job.pooling({
eventsNumber: BLOCKS_LOOP,
ejectorCycleTimer = job.loop({
messageStorage: messageStorage,
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeLogger } from 'lido-nanolib'
import { makeLogger } from './lib/index.js'
import { makeAppModule } from './app/module.js'

const bootstrap = async () => {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './job/index.js'
export * from './logger/index.js'
export * from './request/index.js'
export * from './request/middlewares.js'
export * from './validator/index.js'
53 changes: 53 additions & 0 deletions src/lib/job/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Histogram } from 'prom-client'

import { makeLogger } from '../logger/index.js'

const jobLockMap: Record<string, boolean> = {}

export type JobRunnerService = ReturnType<typeof makeJobRunner>

export const makeJobRunner = <Initial>(
name: string,
{
config,
logger,
metric,
handler,
}: {
config: { JOB_INTERVAL: number }
logger: ReturnType<typeof makeLogger>
metric: Histogram
handler: (handlerValue: Initial) => Promise<void>
}
) => {
const once = async (handlerValue: Initial) => {
if (jobLockMap[name]) return
jobLockMap[name] = true

const end = metric.startTimer({
name,
interval: config.JOB_INTERVAL,
})

try {
logger.debug('Job started', { name })
await handler(handlerValue)
end({ result: 'success' })
} catch (error) {
logger.warn('Job ended with error', error)
end({ result: 'error' })
} finally {
jobLockMap[name] = false
logger.debug('Job ended', { name })
}
}

const loop = (handlerValue: Initial) => {
return setInterval(() => once(handlerValue), config.JOB_INTERVAL)
}

return {
once,
loop,
}
}
47 changes: 47 additions & 0 deletions src/lib/logger/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { printer } from './printer.js'
import { serializeErrorWithCause } from './serialize-error.js'
import type { LoggerOptions, Logger } from './types'

export const LOG_LEVELS = ['error', 'warn', 'log', 'info', 'debug']

export type LoggerService = ReturnType<typeof makeLogger>

export const makeLogger = (options: LoggerOptions) => {
const {
level,
format,
silent,
causeDepth = 3,
sanitizer = { secrets: [], replacer: '*' },
}: LoggerOptions = options
return LOG_LEVELS.reduce((logger, logLevel) => {
logger[logLevel] = (message: string, details?: any) => {
const logLevelOrder = LOG_LEVELS.indexOf(logLevel)
if (LOG_LEVELS.indexOf(level) < logLevelOrder) return
const output: {
message: string
level: string
timestamp: number

details?: unknown
} = {
message,
details,
level: logLevel,
timestamp: Math.floor(Date.now() / 1000),
}

if (details instanceof Error) {
output.details = {
...details,
...serializeErrorWithCause(details, causeDepth),
}
}

const print = format === 'simple' ? printer.simple : printer.json

if (!silent) print(output, logLevel, sanitizer)
}
return logger
}, {}) as Logger
}
Loading
Loading