@lodestar/flare
Version:
Beacon chain debugging tool
113 lines • 4.96 kB
JavaScript
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