UNPKG

@lodestar/flare

Version:
139 lines (117 loc) 4.89 kB
import {SecretKey} from "@chainsafe/blst"; import {getClient} from "@lodestar/api"; import {BeaconConfig, 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 {phase0, ssz} from "@lodestar/types"; import {CliCommand, toPubkeyHex} from "@lodestar/utils"; import {SecretKeysArgs, deriveSecretKeys, secretKeysOptions} from "../util/deriveSecretKeys.js"; type SelfSlashArgs = SecretKeysArgs & { server: string; slot: string; batchSize: string; }; export const selfSlashProposer: CliCommand<SelfSlashArgs, Record<never, never>, void> = { command: "self-slash-proposer", describe: "Self slash validators of a provided mnemonic with ProposerSlashing", examples: [ { command: "self-slash-proposer --network hoodi", 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: SelfSlashArgs): Promise<void> { 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: phase0.BeaconBlockHeaderBigint = { slot, proposerIndex: index, parentRoot: rootA, stateRoot: rootA, bodyRoot: rootA, }; const header2: phase0.BeaconBlockHeaderBigint = { slot, proposerIndex: index, parentRoot: rootB, stateRoot: rootB, bodyRoot: rootB, }; const proposerSlashing: phase0.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 as Error).message = `Error slashing validator ${index}: ${(e as Error).message}`; } }) ); } } function signHeaderBigint(config: BeaconConfig, sk: SecretKey, header: phase0.BeaconBlockHeaderBigint): Uint8Array { const slot = Number(header.slot as bigint); const proposerDomain = config.getDomain(slot, DOMAIN_BEACON_PROPOSER); const signingRoot = computeSigningRoot(ssz.phase0.BeaconBlockHeaderBigint, header, proposerDomain); return sk.sign(signingRoot).toBytes(); }