Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7700ffc
feat(orc-413): remove lastRequestedValidatorIndex
chasingrainbows Jun 11, 2025
e750fb3
feat(orc-413): Updating exit messages left metrics from validator sta…
chasingrainbows Jun 12, 2025
2b3de06
feat(orc-413): refact loop to filter
chasingrainbows Jun 16, 2025
6659870
feat(orc-413): add state param to getExitingValidatorsCount, and impr…
chasingrainbows Jun 17, 2025
d80392b
Merge pull request #143 from lidofinance/main
eddort Jun 17, 2025
eafa92f
Merge pull request #142 from lidofinance/feature/orc-413-restore-exit…
eddort Jun 17, 2025
3d06ee6
feat(orc-409): add withdrawal via voting contracts
chasingrainbows May 30, 2025
80d60a4
feat(orc-409): refact voting events fetching
chasingrainbows Jun 5, 2025
dba9b55
feat(orc-409): add tests
chasingrainbows Jun 5, 2025
7b9bd70
feat(orc-409): rename VOTING_WITHDRAWAL_TRANSACTIONS_ALLOWLIST to SUB…
chasingrainbows Jun 13, 2025
585e3bf
feat(orc-409): add more context for error when ValidatorExitRequest w…
chasingrainbows Jun 13, 2025
8ad400d
feat(orc-409): make EASY_TRACK_ADDRESS env optional
chasingrainbows Jun 18, 2025
4480e87
feat(orc-409): replace DISABLE_SECURITY_DONT_USE_IN_PRODUCTION with T…
chasingrainbows Jun 18, 2025
9c759c1
feat(orc-409): update readme
chasingrainbows Jun 23, 2025
1015c93
feat(orc-409): add validation for getLogs
chasingrainbows Jun 23, 2025
b8bdef6
Merge pull request #141 from lidofinance/feature/orc-409-tw-ejector-o…
eddort Jun 23, 2025
36f3927
feat(orc-428): add getValidatorExitRequestEvents method
chasingrainbows Jul 3, 2025
719f642
feat(orc-428): add validatePublicKeys to cl, use validatePublicKeys i…
chasingrainbows Jul 3, 2025
d156820
feat(orc-428): extract fetchValidatorsBatch method
chasingrainbows Jul 4, 2025
4451aef
feat(orc-428): replace isExiting with batching
chasingrainbows Jul 4, 2025
2470bd8
feat(orc-428): add JobProcessor integration tests
chasingrainbows Jul 4, 2025
097e3f9
feat(orc-428): add stricter typing to fetchValidatorsBatch
chasingrainbows Jul 7, 2025
16691d9
feat(orc-428): remove unnecessary types casting
chasingrainbows Jul 7, 2025
b1f25ad
feat(orc-428): Aragon case fix
chasingrainbows Jul 7, 2025
ae140cb
feat(orc-428): added EASY_TRACK_FRAME_BLOCKS + pre-uploading of votin…
chasingrainbows Jul 7, 2025
f7cd4d5
Merge pull request #144 from lidofinance/feature/orc-428-ejector-chec…
eddort Jul 9, 2025
2bf533f
feat(orc-431): fix endpoint according to Nethermind
chasingrainbows Jul 10, 2025
68b16c2
Merge pull request #145 from lidofinance/feature/orc-431-ejector-fix-…
eddort Jul 10, 2025
44105ac
feat(orc-445): Raise error instead of return currentBlock != highestB…
chasingrainbows Jul 18, 2025
b49afc0
feat(orc-448): add verifyTransactionIntegrity
chasingrainbows Jul 29, 2025
88295d5
feat(orc-448): add test to new logic
chasingrainbows Jul 29, 2025
7a9cf64
feat(orc-448): .github/PULL_REQUEST_TEMPLATE.md added
chasingrainbows Jul 30, 2025
65af229
feat(orc-448): fix contract abi
chasingrainbows Aug 4, 2025
d0e7990
Merge pull request #148 from lidofinance/feature/orc-448-ejector-add-…
eddort Aug 5, 2025
c08a2b6
Merge pull request #147 from lidofinance/feature/orc-445-raise-error-…
eddort Aug 5, 2025
660550e
feat(TW): add type-0 transaction support for SUBMIT_TX_HASH_ALLOWLIST
chasingrainbows Aug 14, 2025
7c0dd15
feat(TW): add legacy type transactions support to recoverAddress
chasingrainbows Aug 14, 2025
c62f5dd
feat(TW): introduce validateTransactionType method
chasingrainbows Aug 14, 2025
3eaf2f6
feat(TW): refactor
chasingrainbows Aug 15, 2025
bc22841
Merge pull request #150 from lidofinance/feature/add-type-0-transacti…
eddort Aug 21, 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
16 changes: 16 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Description
Describe what this pull request does. Explain the purpose of the changes and the problem they solve.

## Related Issue/Task
- Related task: #[task number] (e.g., #123)
- Epic: [epic name or link, if applicable]

## How Has This Been Tested?
Describe how you tested the changes:
- [ ] Local tests (e.g., `yarn test`)
- [ ] Manual testing (describe steps)
- [ ] Not tested (explain why)

## Checklist
- [ ] Documentation updated (if required)
- [ ] New tests added (if applicable)
60 changes: 32 additions & 28 deletions README.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ services:
- MESSAGES_PASSWORD_FILE=${MESSAGES_PASSWORD_FILE}
- VALIDATOR_EXIT_WEBHOOK=${VALIDATOR_EXIT_WEBHOOK}
- ORACLE_ADDRESSES_ALLOWLIST=${ORACLE_ADDRESSES_ALLOWLIST}
- EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST=${EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST}
- SUBMIT_TX_HASH_ALLOWLIST=${SUBMIT_TX_HASH_ALLOWLIST}
- EASY_TRACK_ADDRESS=${EASY_TRACK_ADDRESS}
- BLOCKS_PRELOAD=${BLOCKS_PRELOAD}
- HTTP_PORT=${HTTP_PORT}
- RUN_METRICS=${RUN_METRICS}
Expand All @@ -23,6 +26,7 @@ services:
- LOGGER_FORMAT=${LOGGER_FORMAT}
- LOGGER_SECRETS=${LOGGER_SECRETS}
- DRY_RUN=${DRY_RUN}
- TRUST_MODE=${TRUST_MODE}
ports:
- '${HTTP_PORT}:${HTTP_PORT}'
volumes:
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ services:
- MESSAGES_PASSWORD_FILE=${MESSAGES_PASSWORD_FILE}
- VALIDATOR_EXIT_WEBHOOK=${VALIDATOR_EXIT_WEBHOOK}
- ORACLE_ADDRESSES_ALLOWLIST=${ORACLE_ADDRESSES_ALLOWLIST}
- EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST=${EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST}
- SUBMIT_TX_HASH_ALLOWLIST=${SUBMIT_TX_HASH_ALLOWLIST}
- EASY_TRACK_ADDRESS=${EASY_TRACK_ADDRESS}
- BLOCKS_PRELOAD=${BLOCKS_PRELOAD}
- HTTP_PORT=${HTTP_PORT}
- RUN_METRICS=${RUN_METRICS}
Expand All @@ -24,6 +27,7 @@ services:
- LOGGER_FORMAT=${LOGGER_FORMAT}
- LOGGER_SECRETS=${LOGGER_SECRETS}
- DRY_RUN=${DRY_RUN}
- TRUST_MODE=${TRUST_MODE}
ports:
- '${HTTP_PORT}:${HTTP_PORT}'
volumes:
Expand Down
3 changes: 3 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ MESSAGES_PASSWORD=pass
VALIDATOR_EXIT_WEBHOOK=

ORACLE_ADDRESSES_ALLOWLIST=["0xfdA7E01B2718C511bF016030010572e833C7aE6A","0xD3b1e36A372Ca250eefF61f90E833Ca070559970","0x1a13648EE85386cC101d2D7762e2848372068Bc3","0x3fF28f2EDE8358E288798afC23Ee299a503aD5C9","0x4c75FA734a39f3a21C57e583c1c29942F021C6B7","0xCFd533f909741B78a564e399F64C83B783c27597","0x81E411f1BFDa43493D7994F82fb61A415F6b8Fd4","0xb29dD2f6672C0DFF2d2f173087739A42877A5172","0x3799bDA7B884D33F79CEC926af21160dc47fbe05","0x19b1BEbe4773fEC2496FEf8b81a9c175A823844B","0x7eE534a6081d57AFB25b5Cff627d4D26217BB0E9"]
EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST=[]
SUBMIT_TX_HASH_ALLOWLIST=[]
EASY_TRACK_ADDRESS=0xF0211b7660680B49De1A7E9f25C65660F0a13Fea

BLOCKS_PRELOAD=50000 # 7 days of blocks

Expand Down
3 changes: 3 additions & 0 deletions sample.infra.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ MESSAGES_PASSWORD=pass
VALIDATOR_EXIT_WEBHOOK=

ORACLE_ADDRESSES_ALLOWLIST=["0xfdA7E01B2718C511bF016030010572e833C7aE6A","0xD3b1e36A372Ca250eefF61f90E833Ca070559970","0x1a13648EE85386cC101d2D7762e2848372068Bc3","0x3fF28f2EDE8358E288798afC23Ee299a503aD5C9","0x4c75FA734a39f3a21C57e583c1c29942F021C6B7","0xCFd533f909741B78a564e399F64C83B783c27597","0x81E411f1BFDa43493D7994F82fb61A415F6b8Fd4","0xb29dD2f6672C0DFF2d2f173087739A42877A5172","0x3799bDA7B884D33F79CEC926af21160dc47fbe05","0x19b1BEbe4773fEC2496FEf8b81a9c175A823844B","0x7eE534a6081d57AFB25b5Cff627d4D26217BB0E9"]
EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST=[]
SUBMIT_TX_HASH_ALLOWLIST=[]
EASY_TRACK_ADDRESS=0xF0211b7660680B49De1A7E9f25C65660F0a13Fea

BLOCKS_PRELOAD=50000 # 7 days of blocks

Expand Down
10 changes: 8 additions & 2 deletions src/app/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ export const makeAppModule = async () => {
jwtService
)

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

const consensusApi = makeConsensusApi(
makeRequest([
retry(3),
Expand All @@ -87,6 +85,14 @@ export const makeAppModule = async () => {
config
)

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

const forkVersionResolver = makeForkVersionResolver(
consensusApi,
logger,
Expand Down
38 changes: 29 additions & 9 deletions src/scripts/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '../lib/index.js'
import { makeLoggerConfig, makeConfig } from '../services/config/service.js'
import { makeExecutionApi } from '../services/execution-api/service.js'
import { makeConsensusApi } from '../services/consensus-api/service.js'
import { makeExitLogsService } from '../services/exit-logs/service.js'
import { makeMetrics } from '../services/prom/service.js'

Expand All @@ -33,17 +34,36 @@ const run = async () => {

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

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),
]),
logger,
config
)

const executionApi = makeExecutionApi(executionHttp, logger, config)
const consensusApi = makeConsensusApi(
makeRequest([
retry(3),
loggerMiddleware(logger),
prom(metrics.consensusRequestDurationSeconds),
abort(30_000),
]),
logger,
config
)

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

await executionApi.resolveExitBusAddress()
await executionApi.resolveConsensusAddress()
Expand Down
21 changes: 19 additions & 2 deletions src/services/config/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,23 @@ export const makeConfig = ({
(oracles) => oracles.map(str),
'Please, setup ORACLE_ADDRESSES_ALLOWLIST. Example: ["0x123","0x123"]'
),

EASY_TRACK_ADDRESS: optional(() => str(env.EASY_TRACK_ADDRESS)) ?? '',
EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST:
optional(() =>
json_arr(
env.EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST,
(addresses) => addresses.map(str),
'Please, setup EASY_TRACK_MOTION_CREATOR_ADDRESSES_ALLOWLIST. Example: ["0x123","0x456"]'
)
) ?? [],
SUBMIT_TX_HASH_ALLOWLIST:
optional(() =>
json_arr(
env.SUBMIT_TX_HASH_ALLOWLIST,
(txs) => txs.map(str),
'Please, setup SUBMIT_TX_HASH_ALLOWLIST. Example: ["0x123abc","0x456def"]'
)
) ?? [],
MESSAGES_LOCATION: optional(() => str(env.MESSAGES_LOCATION)),
VALIDATOR_EXIT_WEBHOOK: optional(() => str(env.VALIDATOR_EXIT_WEBHOOK)),

Expand All @@ -65,7 +81,8 @@ export const makeConfig = ({
RUN_HEALTH_CHECK: optional(() => bool(env.RUN_HEALTH_CHECK)) ?? true,

DRY_RUN: optional(() => bool(env.DRY_RUN)) ?? false,
DISABLE_SECURITY_DONT_USE_IN_PRODUCTION:
TRUST_MODE:
optional(() => bool(env.TRUST_MODE)) ??
optional(() => bool(env.DISABLE_SECURITY_DONT_USE_IN_PRODUCTION)) ??
false,
PROM_PREFIX: optional(() => str(env.PROM_PREFIX)),
Expand Down
184 changes: 182 additions & 2 deletions src/services/consensus-api/cl.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { ConsensusApiService, makeConsensusApi } from './service.js'
import { LoggerService, RequestService, makeRequest } from '../../lib/index.js'
import nock from 'nock'
import {
ConsensusApiService,
makeConsensusApi,
FAR_FUTURE_EPOCH,
} from './service.js'
import {
LoggerService,
RequestService,
makeRequest,
makeLogger,
retry,
logger as loggerMiddleware,
abort,
} from '../../lib/index.js'
import {
exitRequestMock,
genesisMock,
Expand Down Expand Up @@ -79,4 +92,171 @@ describe('makeConsensusApi', () => {
// Since exitRequest doesn't return a value, we simply check that no error is thrown
expect(true).toBe(true)
})

it('should correctly count exiting validators in mixed batch', async () => {
const indices = ['1', '2', '3', '4']
const mockResponse = {
data: [
{
index: '1',
status: 'active_ongoing',
validator: { pubkey: '0x123', exit_epoch: '100' },
},
{
index: '2',
status: 'active_ongoing',
validator: { pubkey: '0x456', exit_epoch: FAR_FUTURE_EPOCH },
},
{
index: '3',
status: 'active_ongoing',
validator: { pubkey: '0x789', exit_epoch: '200' },
},
{
index: '4',
status: 'active_ongoing',
validator: { pubkey: '0xabc', exit_epoch: FAR_FUTURE_EPOCH },
},
],
}
nock(config.CONSENSUS_NODE)
.get('/eth/v1/beacon/states/head/validators?id=1,2,3,4')
.reply(200, mockResponse)

const count = await api.getExitingValidatorsCount(indices, 1000)
expect(count).toBe(2)
})

it('should return 0 when validators not found', async () => {
const indices = ['1', '2', '3', '4']
const mockResponse = {
data: [],
}
nock(config.CONSENSUS_NODE)
.get('/eth/v1/beacon/states/head/validators?id=1,2,3,4')
.reply(200, mockResponse)

const count = await api.getExitingValidatorsCount(indices, 1000)
expect(count).toBe(0)
})

it('should handle batch size correctly with large index array', async () => {
const indices = Array.from({ length: 1500 }, (_, i) => (i + 1).toString())
const mockBatch1 = {
data: Array(1000).fill({
index: '1',
status: 'active_ongoing',
validator: { pubkey: '0x123', exit_epoch: '100' },
}),
}
const mockBatch2 = {
data: Array(500).fill({
index: '2',
status: 'active_ongoing',
validator: { pubkey: '0x456', exit_epoch: FAR_FUTURE_EPOCH },
}),
}
nock(config.CONSENSUS_NODE)
.get(
'/eth/v1/beacon/states/head/validators?id=' +
indices.slice(0, 1000).join(',')
)
.reply(200, mockBatch1)
nock(config.CONSENSUS_NODE)
.get(
'/eth/v1/beacon/states/head/validators?id=' +
indices.slice(1000).join(',')
)
.reply(200, mockBatch2)

const count = await api.getExitingValidatorsCount(indices, 1000)
expect(count).toBe(1000)
})

it('should throw an error on server error (500)', async () => {
const indices = ['1']
nock(config.CONSENSUS_NODE)
.get('/eth/v1/beacon/states/head/validators?id=1')
.reply(500, { message: 'Internal Server Error' })

await expect(api.getExitingValidatorsCount(indices, 1000)).rejects.toThrow()
})
})

describe('makeConsensusApi e2e', () => {
let api: ConsensusApiService
let logger: LoggerService
let config: ConfigService

beforeEach(() => {
logger = makeLogger({
level: 'error',
format: 'simple',
})

config = mockConfig(logger, {
CONSENSUS_NODE:
process.env.CONSENSUS_NODE ??
'https://ethereum-beacon-api.publicnode.com',
})
api = makeConsensusApi(
makeRequest([retry(3), loggerMiddleware(logger), abort(30_000)]),
logger,
config
)
})

it('should handle batch size correctly with large index array e2e', async () => {
const indices = Array.from({ length: 3000 }, (_, i) => (i + 1).toString())
const count = await api.getExitingValidatorsCount(indices, 1000, 11724253)
expect(count).toStrictEqual(1226)
})

it('should validate public keys e2e', async () => {
const validatorData = [
{
validatorIndex: '1',
validatorPubkey:
'0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c',
},
{
validatorIndex: '2',
validatorPubkey:
'0xb2ff4716ed345b05dd1dfc6a5a9fa70856d8c75dcc9e881dd2f766d5f891326f0d10e96f3a444ce6c912b69c22c6754d',
},
{
validatorIndex: '3',
validatorPubkey:
'0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
},
]

const validIndices = await api.validatePublicKeys(
validatorData,
1000,
11724253
)
expect(validIndices.size).toEqual(2)
})

it('should fetch validators batch e2e', async () => {
const indices = ['1', '2']
const validators = await api.fetchValidatorsBatch(indices, 1000, 11724253)

expect(validators).toHaveLength(2)

expect(validators[0].index).toBe('1')
expect(validators[0].status).toBe('active_ongoing')
expect(validators[0].validator.pubkey).toBe(
'0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c'
)
expect(validators[0].validator.exit_epoch).toBe('18446744073709551615')

expect(validators[1].index).toBe('2')
expect(validators[1].status).toBe('active_ongoing')
expect(validators[1].validator.pubkey).toBe(
'0xb2ff4716ed345b05dd1dfc6a5a9fa70856d8c75dcc9e881dd2f766d5f891326f0d10e96f3a444ce6c912b69c22c6754d'
)
expect(validators[1].validator.exit_epoch).toBe('18446744073709551615')
})
})
Loading
Loading