lido
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Help
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
1
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- tags: keys-api title: Keys API Service --- # Keys API Service ## Intro Simple Lido keys and validators HTTP API. It will be possible to launch it on someone's machine and fetch actual Lido keys from all sources. API will also provide HTTP methods for [Automating Validator Exits](https://hackmd.io/0xndfK-ISxaOGuEY8FVGSw?view#Generating-and-Signing-Exit-Messages). ## Glossary - `NodeOperatorRegistry` is a Lido contract that manages a сurated set of node operators, stores their signing keys; - `Module` is a contract in charge of different validators subsets; - Module state is a keys, operators and other data for some specific EL block; - Possible validators subsets are `Curated` (modified version of `NodeOperatorRegistry`), `Community`, `DVT`, `Offchain` or `L2` upgrade for Curated subset; - Module type is a value from Staking Module that help to match Module with our code base; - `StakingRouter` is a new contract that is responsible for modules management, reserve allocation and rewards distribution. ## Problem statement Lido node operators keys and validators are used in a lot of services: [Oracle](https://github.com/lidofinance/lido-oracle), [Council Daemon](https://github.com/lidofinance/lido-council-daemon), MEV Monitoring, BalVal, Node Operators Widget. Also Node Operators need fetching keys for automating exits. Currently Lido supports only `Curated` validators subset handled by `NodeOperatorRegistry` contract. But in the future validators set can be expanded with `DVT` and `Community` validators. The `StakingRouter` contract was proposed for implementing modular approach of managing different subsets of validators. It will support any number of `Curated`, `Community` and `DVT` modules. These modules could store keys on-chain, off-chain, on L2 or in any other way. Services will have to support fetching keys from all these modules. ## Assumptions 1. `StakingRouter` will be implemented; 2. `StakingRouter` could support any number `Curated`, `Community`, `DVT`, off-chain and L2 `Curated` modules; 3. Every module will have an `L1` contract; 4. That `L1` contract address will not be changed; 5. Every module can be uniquely identified by it's contract address; 6. Every module will have a type and function to get it; 7. Every module will have `keysOpIndex` or other value to understand that keys list was changed (will be renamed to `nonce`); 8. Curated module will have `operator id`; 9. `Community`/`DVT` modules will have some group id for keys to determine ownership of keys; 10. Used keys will not be removed from any module storage. ## Requirements 1. Keys API should keep all data of modules in a consistent state. In other words Keys API should keep state of all modules identical (`blockHash` in metadata of each module should be equal); 2. Keys API will return it's latest fetched `EL` state. It will NOT return any arbitrary state for arbitrary `blockNumber`. Keys API is NOT an indexer. ## Technical solution ### Overview Keys API service will work with all `StakingRouter` modules. We chose modular way of implementation. For each type of module we will have separate library. Modules data will be stored in separate tables too. For example, we will have `CuratedKey` and `CommunityKey` tables. Every library will contain its own `{ModuleName}Meta` table with EL data. Meta will include `keyOpIndex` (`nonce`) value. API will run cron job to update keys in db for all modules to `latest EL block`. At the moment API supports only `NodeOperatorRegistry` keys. API will have endpoint for [Automating Validator Exits](https://hackmd.io/0xndfK-ISxaOGuEY8FVGSw?view#Generating-and-Signing-Exit-Messages). Information about validators state will be fetched from beacon node and stored in database. We will implement this algorithm as `ValidatorState` library. In the next sections we will describe technical details of each part. ### Modules libraries interface In this section we will describe general interface of libraries for `StakingRouter` modules. Each library will contain methods for fetching keys and other data from modules storages, methods for writing and reading data to the database. ```typescript= // information that shows data relevance interface BlockMetadata { blockNumber: number; blockHash: string; timestamp: number; } // information from staking router module interface ModuleMetadata { // address of module stakingModuleAddress: string; // module type type: string; // unique id of the module moduleId: number; // will be renamed as a nonce keysOpIndex: number; // reward fee of the module moduleFee: number; // treasury fee treasuryFee: number; // target percent of total keys in protocol, in BP targetShare: number; // module status if module can not accept the deposits or can participate in further reward distribution status: number; // name of module name: string; // block.timestamp of the last deposit of the module lastDepositAt: number; // block.number of the last deposit of the module lastDepositBlock: number; } interface Metadata { blockMetadata: BlockMetadata; moduleMetadata: ModuleMetadata; } interface Key { index: number; used: boolean; operatorIndex: number; depositSignature: string; key: string; } interface Operator { index: number; active: boolean; name: string; rewardAddress: string; stakingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface Data<Op extends Operator = Operator,PKey extends Key = Key> { keys: PKey[], operators: Op[], meta: Metadata } interface AbstractModule<Op extends Operator = Operator,PKey extends Key = Key> { stakingModuleAddress: string; type: string; keysOpIndex: number; // method for updating data in database from contract update(blockHash: string): Promise<Metadata>; // methods for reading data from database getKeysFromStorage(): Promise<{keys: PKey[]; meta: Metadata}>; getOperatorsFromStorage(): Promise<{operators: Op[]; meta: Metadata}>; getKeysAndOperatorsFromStorage(): Promise<{keys: PKey[]; operators: Op[]; meta: Metadata}>; getMetadataFromStorage(): Promise<Metadata>; } ``` ### Updating keys in Keys API service As was mentioned in [requirements section](https://hackmd.io/fv8btyNTTOGLZI6LqYyYIg?both#Requirements) Keys API service should keep modules data in storage in consistent state. To fulfill this condition we will fetch data from modules storages for the same `blockHash` and write it in database in transaction. ```typescript= // some function that returns all modules async function getModules(): Promise<AbstractModule[]> { ... } // somewhere in cron jobs const modules = await getModules(); const block = await provider.getBlock('latest'); const blockHash = block.hash; // get all keys, operators, meta entityManager.transactional(async () => { const metadatas = Promise.all(modules.map(async (module) => { return await module.update(blockHash); })); if (!checkMetadatas(metadatas)) { throw new Error('Inconsistent metadatas'); } }); // reading keys somewhere in HTTP controller const keys = await module.getKeysFromStorage(); ``` ### Curated module library As described earlier, at the moment Lido stores keys on-chain in `NodeOperatorRegistry` contract. We have already implemented `Registry` library for fetching keys and operators from this contract, store in database and update database state to `latest EL block`. #### DB Models `Registry` library contains `RegistryKey`, `RegistryOperator`, `RegistryMeta` tables. ```typescript= interface RegistryKey { index: number; key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryOperator { index: number; active: boolean; name: string; rewardAddress: string; stackingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface RegistryMeta { blockNumber: number; blockHash: string; timestamp: number; keysOpIndex: number; } ``` #### Updating keys Updating keys in `Registry` library is based on comparing meta information in contract and database. We read `blockHash`,`blockNumber` values of `latest` block from `EL` and fetch `keyOpIndex` from `NodeOperatorRegistry` by `blockHash`. If current `blockNumber` is less than previous or `keyOpIndex` wasn't changed, keys in database will not be updated. Otherwise, keys and operators will be updated in library's database. ```typescript= async update(blockHashOrBlockTag: string) { const prevMeta: RegistryMeta = await getMetaDataFromStorage(); const currMeta = await getMetaDataFromContract(blockHashOrBlockTag); if (prevMeta.blockNumber > currMeta.blockNumber) { return; } if (prevMeta.keyOpIndex === currMeta.keyOpIndex) { return; } updateOperatorsAndKeysInDB(currMeta.blockHash); } async getMetaDataFromContract(blockHashOrBlockTag): Promise<RegistryMeta> { const block = await provider.getBlock(blockHashOrBlockTag); const blockHash = block.hash; const blockTag = { blockHash }; const keysOpIndex = await registryContract.getKeysOpIndex({ blockTag }); return { blockNumber: block.number, blockHash, keysOpIndex, timestamp: block.timestamp, }; } ``` The same algorithm we consider to have in other modules libraries. ### Validator state library Keys API will also provide endpoints for working with validators. This library will fetch keys from beacon node, store data in database and support it in actual state. #### DB Models Library will contain `Validator`, `Meta` tables. ```typescript= enum VALIDATOR_STATUS { PENDING_INITIALIZED = 'pending_initialized', PENDING_QUEUED = 'pending_queued', ACTIVE_ONGOING = 'active_ongoing', ACTIVE_EXITING = 'active_exiting' ACTIVE_SLASHED = 'active_slashed', EXITED_UNSLASHED = 'exited_unslashed', EXITED_SLASHED = 'exited_slashed', WITHDRAWAL_POSSIBLE = 'withdrawal_possible', WITHDRAWAL_DONE = 'withdrawal_done' } interface Validator { id: number; status: VALIDATOR_STATUS; key: string; } interface Meta { slot: integer; slotRoot: string; // execution layer data blockNumber: integer; blockHash: string; timestamp: integer; } ``` #### Validators approximate update algorithm Algorithm of fetching validators, update and store in database: 1. Fetch `CL` finalized block by [request](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getBlockHeaders) to Beacon node to get `root` and `slot` of last finalized block ```json { "finalized": true "data": { "root": "string", "header": { "message": { "slot": "number" } } } } ``` 2. Compare `slot` from `Meta` table and received `slot` on 1 step. If `Meta` is empty or slot number in database less than received `slot` on 1 step, go on 3 step. Otherwise stop algorithm execution. 3. Fetch EL block information by [request](https://ethereum.github.io/beacon-APIs/#/Beacon/getBlockV2) to Beacon node by slot root parameter. This information is needed to check modules' keys relevance compare to validators in API's database: ``` { "data": { "message": { "description": "The BeaconBlock object from the CL Bellatrix spec.", "body": { "execution_payload": { "description": "The ExecutionPayload object from the CL Bellatrix spec.", "block_number": "number", "timestamp": "string" "block_hash": "string" } } } } } ``` Based on [10 assumption](https://hackmd.io/fv8btyNTTOGLZI6LqYyYIg?both#Assumptions) we need `RegistryMeta.block_number` be newer than or equal to `ValidatorMeta.block_number`. 4. Fetch all validators for `slot_root` received on 1 step by [request](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getStateValidators). 5. Write `Meta` and `Validators` in transaction in database. Cron job with this algorithm will be run every 10 seconds. ```typescript= // validator's library part: interface ConsesusValidatorsAndMetadata { validators: Validator[]; meta: Meta; } class ValidatorsStateService { update() { const CONSENSUS_NODE = 'http://127.0.0.1:5050'; const BLOCK_ID = 'finalized'; const CONSENSUS_BLOCK_HEADERS_ENDPOINT = `${CONSENSUS_NODE}/eth/v1/beacon/headers/${BLOCK_ID}`; const headersResp = await fetch(CONSENSUS_BLOCK_HEADERS_ENDPOINT); const headersJson = await headersResp.json(); const { data: { root: currSlotRoot, header: { message: { slot: currSlot }, }, }, } = headersJson; // Get meta from database const { slot: prevSlot } = await getMetaDataFromStorage(); if ( prevSlot >= currSlot ) { // We have already had validators for this slot return; } const CONSENSUS_BLOCK_DETAILS_ENDPOINT = `${CONSENSUS_NODE}/eth/v2/beacon/blocks/${currSlotRoot}`; // Get EL block information // This information is needed to check RegistryKey data relevance compare to validators in API's database const blockDetailsResp = await fetch(CONSENSUS_BLOCK_DETAILS_ENDPOINT); const blockDetailsJson = await blockDetailsResp.json(); const { data: { message: { body: { execution_payload: { block_number, timestamp, block_hash }, }, }, }, } = blockDetailsJson; const CONSENSUS_STATE_VALIDATORS_ENDPOINT = `${CONSENSUS_NODE}/eth/v1/beacon/states/${currSlotRoot}/validators`; const validators = await fetch(CONSENSUS_STATE_VALIDATORS_ENDPOINT); const validatorsJson = await validators.json(); // save in database const clMeta = { root: currSlotRoot, slot: currSlot, blockNumber: block_number, timestamp, blockHash: block_hash }; storeValidatorsAndMetaTrans(validatorsJson, clMeta); } getValidators(pubkeys: string[]): Promise<ConsesusValidatorsAndMetadata> { ... } getLidoOldestValidators(pubkeys: string[], statuses: string[], validatorPercent: number): Promise<ConsesusValidatorsAndMetadata> { ... } } // Keys API // cron job const job = new CronJob( CronExpression.EVERY_10_SECONDS, () => validatorsStateService.update() ); job.start(); ... // HTTP request getLidoCuratedValidators(operatorId) { const { pubkeys, meta: elMeta } = await registryStorage.getKeysAndMeta({operatorIndex: operatorId, used: true}); const { validators, meta: clMeta} = await validatorsStateService.getValidators(pubkeys); // base on 10 assumption if (elMeta.blockNumber < clMeta.blockNumber ) { throw new InternalServerErrorException(); } return { validators }; } ``` ## API ### /keys **GET** `/v1/keys` Endpoint returns list of keys for all modules. Query: - `used` - filter for used/unused keys. Possible values: true/false; - `operatorIndex` - filter for keys of operator with index `operatorIndex`; ```typescript= interface ELBlockSnapshot { blockNumber: number; blockHash: string; blockTimestamp: number; } interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface ModuleKey extends Key { moduleAddress: string; } class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/keys' \ -H 'accept: application/json' ``` :::warning Response of this endpoint could be very large but we can’t have a pagination here since data could be updated in the process. ::: :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/keys/{pubkey}` Return key by public key with basic fields. `pubkey` should be in lowercase. ```typescript= class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3005/v1/keys/pubkey' \ -H 'accept: application/json' ``` **POST** `/v1/keys/find` Returns all keys found in db. Request body: ```typescript= interface RequestBody { // public keys in lowercase pubkeys: string[]; } ``` Response: ```typescript= class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ElBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } ``` Example: ``` curl -X 'POST' \ 'http://localhost:3000/v1/keys/find' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{"pubkeys": ["pubkey0 "]}' ``` :::warning If API returns 425 code, it means database is not ready for work ::: ### /modules **GET** `/v1/modules` Endpoint returns list of staking router modules. ```typescript= interface Module { nonce: number; type: string; // unique id of the module id: number; // address of module stakingModuleAddress: string; // rewarf fee of the module moduleFee: number; // treasury fee treasuryFee: number; // target percent of total keys in protocol, in BP targetShare: number; // module status if module can not accept the deposits or can participate in further reward distribution status: number; // name of module name: string; // block.timestamp of the last deposit of the module lastDepositAt: number; // block.number of the last deposit of the module lastDepositBlock: number; } interface ELBlockSnapshot { blockNumber: number, blockHash: string, blockTimestamp: number, } class Reponse { data: Module[]; elBlockSnapshot: ElBlockSnapshot; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}` `module_id` - staking router module contact address or id; Endpoint return information about staking router module; ```typescript= interface Module { nonce: number; type: string; /// @notice unique id of the module id: number; /// @notice address of module stakingModuleAddress: string; /// @notice rewarf fee of the module moduleFee: number; /// @notice treasury fee treasuryFee: number; /// @notice target percent of total keys in protocol, in BP targetShare: number; /// @notice module status if module can not accept the deposits or can participate in further reward distribution status: number; /// @notice name of module name: string; /// @notice block.timestamp of the last deposit of the module lastDepositAt: number; /// @notice block.number of the last deposit of the module lastDepositBlock: number; } interface ELBlockSnapshot { blockNumber: number; blockHash: string; blockTimestamp: number; } class Reponse { data: Module; elBlockSnapshot: ElBlockSnapshot; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundResponse implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/1' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: ### /modules/keys/ **GET** `/v1/modules/keys/` Return keys for all modules grouped by staking router module. Query: - `used` - filter for used/unused keys. Possible values: true/false; - `operatorIndex` - filter for keys of operator with index `operatorIndex`; ```typescript= interface Module { // current KeyOpIndex nonce: number; // type of module type: string; /// @notice unique id of the module id: number; /// @notice address of module stakingModuleAddress: string; /// @notice rewarf fee of the module moduleFee: number; /// @notice treasury fee treasuryFee: number; /// @notice target percent of total keys in protocol, in BP targetShare: number; /// @notice module status if module can not accept the deposits or can participate in further reward distribution status: number; /// @notice name of module name: string; /// @notice block.timestamp of the last deposit of the module lastDepositAt: number; /// @notice block.number of the last deposit of the module lastDepositBlock: number; } class Response{ data: { keys: Key[]; module: Module; }[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/keys?used=true&operatorIndex=1' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}/keys` `module_id` - staking router module contact address or id; Endpoint returns list of keys for module. Query: - `used` - filter for used/unused keys. Possible values: true/false; - `operatorIndex` - filter for keys of operator with index `operatorIndex`; Response: Response depends on `module type` ```typescript= interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryKey extends Key { index: number; } interface CommunityKey extends Key { } class Response { data: { keys: RegistryKey[] | CommunityKey[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/1/keys?used=true&operatorIndex=1' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **POST** `/v1/modules/{module_id}/keys/find` `module_id` - staking router module contact address or id; Returns all keys found in db. Request body: ```typescript interface RequestBody { // public keys in lowercase pubkeys: string[]; } ``` Response: ```typescript= interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryKey extends Key { index: number; } interface CommunityKey extends Key { } class Response { data: { keys: RegistryKey[] | CommunityKey[]; module: Module; }; meta: { elBlockSnapshot: ElBlockSnapshot } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'POST' \ 'http://localhost:3000/v1/modules/1/keys/find' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "pubkeys": [ "pubkey" ] }' ``` :::warning If API returns 425 code, it means database is not ready for work ::: ### /validators **GET** `/v1/modules/{module_id}/validators/validator-exits-to-prepare/{operator_id}` `module_id` - staking router module contact address or id; This endpoint will return N of oldest lido validators for earliest epoch when voluntary exit can be processed for specific node operator and specific `StakingRouter` module. Node operator will use this validators list for preparing pre-sign exit messages. API will find `used` keys of node operator and find for these public keys N oldest validators in `Validator` table. We consider active validators (`active_ongoing` status) or validators in `pending_initialized`, `pending_queued` statuses. Module tables state fetched from `EL` should be newer than `Validator` table state fetched from `CL`. Otherwise API will return error on request. Query: Only one filter is available. If both parameters are provided, `percent` has a high priority. - `percent` - Percent of validators to exit. Default value is 10. - `max_amount` - Number of validators to exit. If validators number less than amount, return all validators. ```typescript= interface Validator { validatorIndex: number; key: string; } interface CLBlockSnapshot { epoch: number; root: number; slot: number; blockNumber: number; timestamp: number; blockHash: string; }; class Response { data: Validator[]; meta: { clBlockSnapshot: CLBlockSnapshot; }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } class InternalServerErrorExceptionNotActualData implements HttpException { statusCode: number = 500; message: string = 'Last Execution Layer block number in our database older than last Consensus Layer'; } class InternalServerErrorExceptionDisable implements HttpException { statusCode: number = 500; message: string = 'Validators Registry is disabled. Check environment variables'; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/1/validators/validator-exits-to-prepare/1?percent=10' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}/validators/generate-unsigned-exit-messages/{operator_id}` `module_id` - staking router module contact address or id; Return unsigned exit messages for N oldest validators for earliest epoch when voluntary exit can be processed. Query: Only one filter is available. If both parameters are provided, `percent` has a high priority. - `percent` - Percent of validators to exit. Default value is 10. - `max_amount` - Number of validators to exit. If validators number less than amount, return all validators. ```typescript= interface ExitPresignMessage { validator_index: string; epoch: string; } interface CLBlockSnapshot { epoch: number; root: string; slot: number; blockNumber: number; timestamp: number; blockHash: string; }; class Response { data: ExitPresignMessage[]; meta: { clBlockSnapshot: CLBlockSnapshot; } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } class InternalServerErrorExceptionNotActualData implements HttpException { statusCode: number = 500; message: string = 'Last Execution Layer block number in our database older than last Consensus Layer'; } class InternalServerErrorExceptionDisable implements HttpException { statusCode: number = 500; message: string = 'Validators Registry is disabled. Check environment variables'; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/1/validators/generate-unsigned-exit-messages/1?percent=10' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: Approximate algorithm: ```typescript= getExitsPresign(operatorId: number, messages: boolean) { const { keys: pubkeys, meta: elMeta } = await registryStorage.getKeysAndMeta({operatorIndex: operatorId, used: true}); const { validators, meta: clMeta } = await ValidatorsLibrary.getLidoOldestValidators(pubkeys, ['active_ongoing', 'pending_initialized', 'pending_queued'], 10) if (elMeta.blockNumber < clMeta.blockNumber ) { throw new InternalServerErrorException(); } if (messages) { return validators.map((v) => ({ validator_index: v.id, epoch: calculateEpoch(clMeta.slot) }) } return validators.map((v) => ({ validator_index: v.id, key: v.key})); } calculateEpoch(slot) {} ``` ### /operators **GET** `/v1/operators` List of operators grouped by staking router module Query ```typescript= interface Operator { index: number; active: boolean; } interface CuratedOperator extends Operator { name: string; rewardAddress: string; stakingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface CommunityOperator extends Operator { } class Response { data: { operators: CuratedOperator[] | CommunityOperator[]; module: Module; }[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/operators' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}/operators/` `module_id` - staking router module contact address or id; List of SR module operators ```typescript= class Response { data: { operators: CuratedOperator[] | CommunityOperator[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3000/v1/modules/1/operators' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}/operators/{operator_id}` `module_id` - staking router module contact address or id; `operator_id` - operator index; List of SR module operators ```typescript= class Response { data: { operators: CuratedOperator | CommunityOperator; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3005/v1/modules/1/operators/1' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: **GET** `/v1/modules/{module_id}/operators/keys` `module_id` - staking router module contact address or id; Query: - `used` - filter for used/unused keys. Possible values: true/false; - `operatorIndex` - filter for keys of operator with index `operatorIndex`; ```typescript= class Response { data: { keys: RegistryKey[] | CommunityKey[]; operators: CuratedOperator[] | CommunityOperator[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } ``` Example: ``` curl -X 'GET' \ 'http://localhost:3005/v1/modules/1/operators/1' \ -H 'accept: application/json' ``` :::warning If API returns 425 code, it means database is not ready for work ::: ### /status **GET** /v1/status ```typescript= class Response { // keys api version appVersion: string; chainId: number; elBlockSnapshot: ELBlockSnapshot; clBlockSnapshot: CLBlockSnapshot; } ``` ## Prometheus metrics ## Roadmap #### Future iterations - Make modules optional; - Research possibility of gRPC as a transport if Keys API is used as a microservice; - Implement absraction for staking router ;(possibility to have flexible list of modules, choose chain etc); - Use this service without db as CLI;

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully