UNPKG

@lodestar/flare

Version:
121 lines 5.48 kB
import { aggregateSignatures } from "@chainsafe/blst"; import { getClient } from "@lodestar/api"; import { createBeaconConfig } from "@lodestar/config"; import { config as chainConfig } from "@lodestar/config/default"; import { DOMAIN_BEACON_ATTESTER, MAX_VALIDATORS_PER_COMMITTEE } 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 selfSlashAttester = { command: "self-slash-attester", describe: "Self slash validators of a provided mnemonic with AttesterSlashing", 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: "AttesterSlashing data slot", default: "0", type: "string", }, batchSize: { description: "Add batchSize validators in each AttesterSlashing. Must be < MAX_VALIDATORS_PER_COMMITTEE", default: "10", type: "string", }, }, handler: selfSlashAttesterHandler, }; export async function selfSlashAttesterHandler(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}`); if (batchSize > MAX_VALIDATORS_PER_COMMITTEE) throw Error("batchSize must be < MAX_VALIDATORS_PER_COMMITTEE"); // 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(); // All validators in the batch will be part of the same AttesterSlashing const attestingIndices = validators.map((v) => v.index); // Submit all ProposerSlashing for range at once // Ensure sorted response for (let i = 0; i < pksHex.length; i++) { const { index, status, validator } = validators[i]; const pkHex = pksHex[i]; 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`); } } // Trigers a double vote, same target epoch different data (beaconBlockRoot) // TODO: Allow to create double-votes const data1 = { slot, index: BigInt(0), beaconBlockRoot: rootA, source: { epoch: BigInt(0), root: rootA }, target: { epoch: BigInt(0), root: rootB }, }; const data2 = { slot, index: BigInt(0), beaconBlockRoot: rootB, source: { epoch: BigInt(0), root: rootA }, target: { epoch: BigInt(0), root: rootB }, }; const attesterSlashing = { attestation1: { attestingIndices, data: data1, signature: signAttestationDataBigint(config, sks, data1), }, attestation2: { attestingIndices, data: data2, signature: signAttestationDataBigint(config, sks, data2), }, }; (await client.beacon.submitPoolAttesterSlashingsV2({ attesterSlashing })).assertOk(); successCount += attestingIndices.length; const indexesStr = attestingIndices.join(","); console.log(`Submitted self AttesterSlashing for validators ${indexesStr} - ${successCount}/${totalCount}`); } } function signAttestationDataBigint(config, sks, data) { const slot = Number(data.slot); const proposerDomain = config.getDomain(slot, DOMAIN_BEACON_ATTESTER); const signingRoot = computeSigningRoot(ssz.phase0.AttestationDataBigint, data, proposerDomain); const sigs = sks.map((sk) => sk.sign(signingRoot)); return aggregateSignatures(sigs).toBytes(); } //# sourceMappingURL=selfSlashAttester.js.map