UNPKG

@lodestar/flare

Version:
113 lines 4.96 kB
import { getClient } from "@lodestar/api"; import { createBeaconConfig } from "@lodestar/config"; import { config as chainConfig } from "@lodestar/config/default"; import { DOMAIN_BEACON_PROPOSER } from "@lodestar/params"; import { computeSigningRoot } from "@lodestar/state-transition"; import { ssz } from "@lodestar/types"; import { toPubkeyHex } from "@lodestar/utils"; import { deriveSecretKeys, secretKeysOptions } from "../util/deriveSecretKeys.js"; export const selfSlashProposer = { command: "self-slash-proposer", describe: "Self slash validators of a provided mnemonic with ProposerSlashing", examples: [ { command: "self-slash-proposer --network holesky", description: "Self slash validators of a provided mnemonic", }, ], options: { ...secretKeysOptions, server: { description: "Address to connect to BeaconNode", default: "http://127.0.0.1:9596", type: "string", }, slot: { description: "ProposerSlashing headers slot", default: "0", type: "string", }, batchSize: { description: "Send in batches of size batchSize", default: "10", type: "string", }, }, handler: selfSlashProposerHandler, }; export async function selfSlashProposerHandler(args) { const sksAll = deriveSecretKeys(args); const slot = BigInt(args.slot); // Throws if not valid const batchSize = parseInt(args.batchSize); if (Number.isNaN(batchSize)) throw Error(`Invalid arg batchSize ${args.batchSize}`); if (batchSize <= 0) throw Error(`batchSize must be > 0: ${batchSize}`); // TODO: Ask the user to confirm the range and slash action const client = getClient({ baseUrl: args.server }, { config: chainConfig }); // Get genesis data to perform correct signatures const { genesisValidatorsRoot } = (await client.beacon.getGenesis()).value(); const config = createBeaconConfig(chainConfig, genesisValidatorsRoot); // TODO: Allow to customize the ProposerSlashing payloads const rootA = Buffer.alloc(32, 0xaa); const rootB = Buffer.alloc(32, 0xbb); // To log progress let successCount = 0; const totalCount = sksAll.length; for (let n = 0; n < sksAll.length; n += batchSize) { const sks = sksAll.slice(n, n + batchSize); // Retrieve the status all all validators in range at once const pksHex = sks.map((sk) => sk.toPublicKey().toHex()); const validators = (await client.beacon.postStateValidators({ stateId: "head", validatorIds: pksHex })).value(); // Submit all ProposerSlashing for range at once await Promise.all(pksHex.map(async (pkHex, i) => { const sk = sks[i]; const { index, status, validator } = validators[i]; try { const validatorPkHex = toPubkeyHex(validator.pubkey); if (validatorPkHex !== pkHex) { throw Error(`Beacon node did not return same validator pubkey: ${validatorPkHex} != ${pkHex}`); } if (status === "active_slashed" || status === "exited_slashed") { console.log(`Warning: validator index ${index} is already slashed`); } const header1 = { slot, proposerIndex: index, parentRoot: rootA, stateRoot: rootA, bodyRoot: rootA, }; const header2 = { slot, proposerIndex: index, parentRoot: rootB, stateRoot: rootB, bodyRoot: rootB, }; const proposerSlashing = { signedHeader1: { message: header1, signature: signHeaderBigint(config, sk, header1), }, signedHeader2: { message: header2, signature: signHeaderBigint(config, sk, header2), }, }; (await client.beacon.submitPoolProposerSlashings({ proposerSlashing })).assertOk(); console.log(`Submitted self ProposerSlashing for validator ${index} - ${++successCount}/${totalCount}`); } catch (e) { e.message = `Error slashing validator ${index}: ${e.message}`; } })); } } function signHeaderBigint(config, sk, header) { const slot = Number(header.slot); const proposerDomain = config.getDomain(slot, DOMAIN_BEACON_PROPOSER); const signingRoot = computeSigningRoot(ssz.phase0.BeaconBlockHeaderBigint, header, proposerDomain); return sk.sign(signingRoot).toBytes(); } //# sourceMappingURL=selfSlashProposer.js.map