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