Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: toPubkeyHex #7065

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@lodestar/types";
import {ForkName, isForkBlobs} from "@lodestar/params";
import {ChainForkConfig} from "@lodestar/config";
import {toPubkeyHex} from "@lodestar/utils";

import {Endpoint, RouteDefinitions, Schema} from "../utils/index.js";
import {MetaHeader, VersionCodec, VersionMeta} from "../utils/metadata.js";
Expand Down Expand Up @@ -105,7 +106,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
method: "GET",
req: {
writeReq: ({slot, parentHash, proposerPubkey: proposerPubKey}) => ({
params: {slot, parent_hash: toHexString(parentHash), pubkey: toHexString(proposerPubKey)},
params: {slot, parent_hash: toHexString(parentHash), pubkey: toPubkeyHex(proposerPubKey)},
}),
parseReq: ({params}) => ({
slot: params.slot,
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/cmds/validator/keymanager/keystoreCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from "node:path";
import {Keystore} from "@chainsafe/bls-keystore";
import {SecretKey} from "@chainsafe/blst";
import {SignerLocal, SignerType} from "@lodestar/validator";
import {fromHex, toHex} from "@lodestar/utils";
import {fromHex, toHex, toPubkeyHex} from "@lodestar/utils";
import {writeFile600Perm} from "../../../util/file.js";
import {lockFilepath, unlockFilepath} from "../../../util/lockfile.js";
import {LocalKeystoreDefinition} from "./interface.js";
Expand Down Expand Up @@ -42,9 +42,9 @@ export async function loadKeystoreCache(
const secretKey = SecretKey.fromBytes(secretKeyBytes);
const publicKey = secretKey.toPublicKey().toBytes();

if (toHex(publicKey) !== toHex(fromHex(k.pubkey))) {
if (toPubkeyHex(publicKey) !== toPubkeyHex(fromHex(k.pubkey))) {
throw new Error(
`Keystore ${k.uuid} does not match the expected pubkey. expected=${toHex(fromHex(k.pubkey))}, found=${toHex(
`Keystore ${k.uuid} does not match the expected pubkey. expected=${toPubkeyHex(fromHex(k.pubkey))}, found=${toHex(
publicKey
)}`
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import path from "node:path";
import {toHexString} from "@chainsafe/ssz";
import {InterchangeFormatVersion} from "@lodestar/validator";
import {getNodeLogger} from "@lodestar/logger/node";
import {CliCommand} from "@lodestar/utils";
import {CliCommand, toPubkeyHex} from "@lodestar/utils";
import {YargsError, ensure0xPrefix, isValidatePubkeyHex, writeFile600Perm} from "../../../util/index.js";
import {parseLoggerArgs} from "../../../util/logger.js";
import {GlobalArgs} from "../../../options/index.js";
Expand Down Expand Up @@ -86,7 +85,7 @@ export const exportCmd: CliCommand<ExportArgs, ISlashingProtectionArgs & Account
if (!isValidatePubkeyHex(pubkeyHex)) {
throw new YargsError(`Invalid pubkey ${pubkeyHex}`);
}
const existingPubkey = allPubkeys.find((pubkey) => toHexString(pubkey) === pubkeyHex);
const existingPubkey = allPubkeys.find((pubkey) => toPubkeyHex(pubkey) === pubkeyHex);
if (!existingPubkey) {
logger.warn("Pubkey not found in slashing protection db", {pubkey: pubkeyHex});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/cmds/validator/voluntaryExit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@lodestar/state-transition";
import {createBeaconConfig, BeaconConfig} from "@lodestar/config";
import {phase0, ssz, ValidatorIndex, Epoch} from "@lodestar/types";
import {CliCommand, fromHex, toHex} from "@lodestar/utils";
import {CliCommand, fromHex, toPubkeyHex} from "@lodestar/utils";
import {externalSignerPostSignature, SignableMessageType, Signer, SignerType} from "@lodestar/validator";
import {ApiClient, getClient} from "@lodestar/api";
import {ensure0xPrefix, YargsError, wrapError} from "../../util/index.js";
Expand Down Expand Up @@ -209,7 +209,7 @@ async function resolveValidatorIndexes(client: ApiClient, signersToExit: SignerP

const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pubkeys})).value();

const dataByPubkey = new Map(validators.map((item) => [toHex(item.validator.pubkey), item]));
const dataByPubkey = new Map(validators.map((item) => [toPubkeyHex(item.validator.pubkey), item]));

return signersToExit.map(({signer, pubkey}) => {
const item = dataByPubkey.get(pubkey);
Expand Down
4 changes: 2 additions & 2 deletions packages/flare/src/cmds/selfSlashAttester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {AttesterSlashing, phase0, ssz} from "@lodestar/types";
import {config as chainConfig} from "@lodestar/config/default";
import {createBeaconConfig, BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_ATTESTER, MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
import {CliCommand, toHexString} from "@lodestar/utils";
import {CliCommand, toPubkeyHex} from "@lodestar/utils";
import {computeSigningRoot} from "@lodestar/state-transition";
import {deriveSecretKeys, SecretKeysArgs, secretKeysOptions} from "../util/deriveSecretKeys.js";

Expand Down Expand Up @@ -90,7 +90,7 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise<voi
for (let i = 0; i < pksHex.length; i++) {
const {index, status, validator} = validators[i];
const pkHex = pksHex[i];
const validatorPkHex = toHexString(validator.pubkey);
const validatorPkHex = toPubkeyHex(validator.pubkey);
if (validatorPkHex !== pkHex) {
throw Error(`getStateValidators did not return same validator pubkey: ${validatorPkHex} != ${pkHex}`);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/flare/src/cmds/selfSlashProposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {phase0, ssz} from "@lodestar/types";
import {config as chainConfig} from "@lodestar/config/default";
import {createBeaconConfig, BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
import {CliCommand, toHexString} from "@lodestar/utils";
import {CliCommand, toPubkeyHex} from "@lodestar/utils";
import {computeSigningRoot} from "@lodestar/state-transition";
import {deriveSecretKeys, SecretKeysArgs, secretKeysOptions} from "../util/deriveSecretKeys.js";

Expand Down Expand Up @@ -86,7 +86,7 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise<voi
const {index, status, validator} = validators[i];

try {
const validatorPkHex = toHexString(validator.pubkey);
const validatorPkHex = toPubkeyHex(validator.pubkey);
if (validatorPkHex !== pkHex) {
throw Error(`getStateValidators did not return same validator pubkey: ${validatorPkHex} != ${pkHex}`);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/state-transition/src/cache/syncCommitteeCache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {CompositeViewDU, toHexString} from "@chainsafe/ssz";
import {CompositeViewDU} from "@chainsafe/ssz";
import {ssz, ValidatorIndex} from "@lodestar/types";
import {toPubkeyHex} from "@lodestar/utils";
import {PubkeyIndexMap} from "./pubkeyCache.js";

type SyncComitteeValidatorIndexMap = Map<ValidatorIndex, number[]>;
Expand Down Expand Up @@ -82,7 +83,7 @@ function computeSyncCommitteeIndices(
for (const pubkey of pubkeys) {
const validatorIndex = pubkey2index.get(pubkey);
if (validatorIndex === undefined) {
throw Error(`SyncCommittee pubkey is unknown ${toHexString(pubkey)}`);
throw Error(`SyncCommittee pubkey is unknown ${toPubkeyHex(pubkey)}`);
}

validatorIndices.push(validatorIndex);
Expand Down
71 changes: 46 additions & 25 deletions packages/utils/src/bytes/browser.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
// "0".charCodeAt(0) = 48
const CHAR_CODE_0 = 48;
// "x".charCodeAt(0) = 120
const CHAR_CODE_X = 120;

export function toHex(bytes: Uint8Array): string {
const charCodes = new Array<number>(bytes.length * 2 + 2);
charCodes[0] = 48;
charCodes[1] = 120;

for (let i = 0; i < bytes.length; i++) {
const byte = bytes[i];
const first = (byte & 0xf0) >> 4;
const second = byte & 0x0f;
charCodes[0] = CHAR_CODE_0;
charCodes[1] = CHAR_CODE_X;

// "0".charCodeAt(0) = 48
// "a".charCodeAt(0) = 97 => delta = 87
charCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
charCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
}
bytesIntoCharCodes(bytes, charCodes);
return String.fromCharCode(...charCodes);
}

const rootCharCodes = new Array<number>(32 * 2 + 2);
// "0".charCodeAt(0)
rootCharCodes[0] = 48;
// "x".charCodeAt(0)
rootCharCodes[1] = 120;
rootCharCodes[0] = CHAR_CODE_0;
rootCharCodes[1] = CHAR_CODE_X;

/**
* Convert a Uint8Array, length 32, to 0x-prefixed hex string
Expand All @@ -30,17 +24,24 @@ export function toRootHex(root: Uint8Array): string {
throw Error(`Expect root to be 32 bytes, got ${root.length}`);
}

for (let i = 0; i < root.length; i++) {
const byte = root[i];
const first = (byte & 0xf0) >> 4;
const second = byte & 0x0f;
bytesIntoCharCodes(root, rootCharCodes);
return String.fromCharCode(...rootCharCodes);
}

// "0".charCodeAt(0) = 48
// "a".charCodeAt(0) = 97 => delta = 87
rootCharCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
rootCharCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
const pubkeyCharCodes = new Array<number>(48 * 2 + 2);
pubkeyCharCodes[0] = CHAR_CODE_0;
pubkeyCharCodes[1] = CHAR_CODE_X;

/**
* Convert a Uint8Array, length 48, to 0x-prefixed hex string
*/
export function toPubkeyHex(pubkey: Uint8Array): string {
if (pubkey.length !== CHAR_CODE_0) {
throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
}
return String.fromCharCode(...rootCharCodes);

bytesIntoCharCodes(pubkey, pubkeyCharCodes);
return String.fromCharCode(...pubkeyCharCodes);
}

export function fromHex(hex: string): Uint8Array {
Expand All @@ -64,3 +65,23 @@ export function fromHex(hex: string): Uint8Array {
}
return bytes;
}

/**
* Populate charCodes from bytes. Note that charCodes index 0 and 1 ("0x") are not populated.
*/
function bytesIntoCharCodes(bytes: Uint8Array, charCodes: number[]): void {
if (bytes.length * 2 + 2 !== charCodes.length) {
throw Error(`Expect charCodes to be of length ${bytes.length * 2 + 2}, got ${charCodes.length}`);
}

for (let i = 0; i < bytes.length; i++) {
const byte = bytes[i];
const first = (byte & 0xf0) >> 4;
const second = byte & 0x0f;

// "0".charCodeAt(0) = 48
// "a".charCodeAt(0) = 97 => delta = 87
charCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
charCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
}
}
18 changes: 15 additions & 3 deletions packages/utils/src/bytes/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import {toHex as browserToHex, toRootHex as browserToRootHex, fromHex as browserFromHex} from "./browser.js";
import {toHex as nodeToHex, toRootHex as nodeToRootHex, fromHex as nodeFromHex} from "./nodejs.js";
import {
toHex as browserToHex,
toRootHex as browserToRootHex,
fromHex as browserFromHex,
toPubkeyHex as browserToPubkeyHex,
} from "./browser.js";
import {
toHex as nodeToHex,
toRootHex as nodeToRootHex,
fromHex as nodeFromHex,
toPubkeyHex as nodeToPubkeyHex,
} from "./nodejs.js";

let toHex = browserToHex;
let toRootHex = browserToRootHex;
let toPubkeyHex = browserToPubkeyHex;
let fromHex = browserFromHex;

if (typeof Buffer !== "undefined") {
toHex = nodeToHex;
toRootHex = nodeToRootHex;
toPubkeyHex = nodeToPubkeyHex;
fromHex = nodeFromHex;
}

export {toHex, toRootHex, fromHex};
export {toHex, toRootHex, toPubkeyHex, fromHex};
16 changes: 16 additions & 0 deletions packages/utils/src/bytes/nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ export function toRootHex(root: Uint8Array): string {
return `0x${rootBuf.toString("hex")}`;
}

// Shared buffer to convert pubkey to hex
let pubkeyBuf: Buffer | undefined;

export function toPubkeyHex(pubkey: Uint8Array): string {
if (pubkey.length !== 48) {
throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
}

if (pubkeyBuf === undefined) {
pubkeyBuf = Buffer.alloc(48);
}

pubkeyBuf.set(pubkey);
return `0x${pubkeyBuf.toString("hex")}`;
}

export function fromHex(hex: string): Uint8Array {
const b = Buffer.from(hex.replace("0x", ""), "hex");
return new Uint8Array(b.buffer, b.byteOffset, b.length);
Expand Down
21 changes: 20 additions & 1 deletion packages/utils/test/unit/bytes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, it, expect} from "vitest";
import {intToBytes, bytesToInt, toHex, fromHex, toHexString, toRootHex} from "../../src/index.js";
import {intToBytes, bytesToInt, toHex, fromHex, toHexString, toRootHex, toPubkeyHex} from "../../src/index.js";

describe("intToBytes", () => {
const zeroedArray = (length: number): number[] => Array.from({length}, () => 0);
Expand Down Expand Up @@ -80,6 +80,25 @@ describe("toRootHex", () => {
}
});

describe("toPubkeyHex", () => {
const testCases: {input: Uint8Array; output: string}[] = [
{
input: new Uint8Array(Array.from({length: 48}, (_, i) => i)),
output: "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
},
{
input: new Uint8Array(Array.from({length: 48}, () => 0)),
output: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
];

for (const {input, output} of testCases) {
it(`should convert root to hex string ${output}`, () => {
expect(toPubkeyHex(input)).toBe(output);
});
}
});

describe("fromHex", () => {
const testCases: {input: string; output: Buffer | Uint8Array}[] = [
{
Expand Down
7 changes: 3 additions & 4 deletions packages/validator/src/services/attestationDuties.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {toHexString} from "@chainsafe/ssz";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {sleep} from "@lodestar/utils";
import {sleep, toPubkeyHex} from "@lodestar/utils";
import {computeEpochAtSlot, isAggregatorFromCommitteeLength, isStartSlotOfEpoch} from "@lodestar/state-transition";
import {BLSSignature, Epoch, Slot, ValidatorIndex, RootHex} from "@lodestar/types";
import {ApiClient, routes} from "@lodestar/api";
Expand Down Expand Up @@ -97,7 +96,7 @@ export class AttestationDutiesService {
removeDutiesForKey(pubkey: PubkeyHex): void {
for (const [epoch, attDutiesAtEpoch] of this.dutiesByIndexByEpoch) {
for (const [vIndex, attDutyAndProof] of attDutiesAtEpoch.dutiesByIndex) {
if (toHexString(attDutyAndProof.duty.pubkey) === pubkey) {
if (toPubkeyHex(attDutyAndProof.duty.pubkey) === pubkey) {
attDutiesAtEpoch.dutiesByIndex.delete(vIndex);
if (attDutiesAtEpoch.dutiesByIndex.size === 0) {
this.dutiesByIndexByEpoch.delete(epoch);
Expand Down Expand Up @@ -244,7 +243,7 @@ export class AttestationDutiesService {
const attesterDuties = res.value();
const {dependentRoot} = res.meta();
const relevantDuties = attesterDuties.filter((duty) => {
const pubkeyHex = toHexString(duty.pubkey);
const pubkeyHex = toPubkeyHex(duty.pubkey);
return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex);
});

Expand Down
5 changes: 2 additions & 3 deletions packages/validator/src/services/block.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {toHexString} from "@chainsafe/ssz";
import {
BLSPubkey,
Slot,
Expand All @@ -15,7 +14,7 @@ import {
} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {ForkPreBlobs, ForkBlobs, ForkSeq, ForkExecution, ForkName} from "@lodestar/params";
import {extendError, prettyBytes, prettyWeiToEth} from "@lodestar/utils";
import {extendError, prettyBytes, prettyWeiToEth, toPubkeyHex} from "@lodestar/utils";
import {ApiClient, routes} from "@lodestar/api";
import {IClock, LoggerVc} from "../util/index.js";
import {PubkeyHex} from "../types.js";
Expand Down Expand Up @@ -110,7 +109,7 @@ export class BlockProposingService {

/** Produce a block at the given slot for pubkey */
private async createAndPublishBlock(pubkey: BLSPubkey, slot: Slot): Promise<void> {
const pubkeyHex = toHexString(pubkey);
const pubkeyHex = toPubkeyHex(pubkey);
const logCtx = {slot, validator: prettyBytes(pubkeyHex)};

// Wrap with try catch here to re-use `logCtx`
Expand Down
9 changes: 4 additions & 5 deletions packages/validator/src/services/blockDuties.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {toHexString} from "@chainsafe/ssz";
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {BLSPubkey, Epoch, RootHex, Slot} from "@lodestar/types";
import {ApiClient, routes} from "@lodestar/api";
import {sleep} from "@lodestar/utils";
import {sleep, toPubkeyHex} from "@lodestar/utils";
import {ChainConfig} from "@lodestar/config";
import {IClock, differenceHex, LoggerVc} from "../util/index.js";
import {PubkeyHex} from "../types.js";
Expand Down Expand Up @@ -67,7 +66,7 @@ export class BlockDutiesService {
if (dutyAtEpoch) {
for (const proposer of dutyAtEpoch.data) {
if (proposer.slot === slot) {
publicKeys.set(toHexString(proposer.pubkey), proposer.pubkey);
publicKeys.set(toPubkeyHex(proposer.pubkey), proposer.pubkey);
}
}
}
Expand All @@ -78,7 +77,7 @@ export class BlockDutiesService {
removeDutiesForKey(pubkey: PubkeyHex): void {
for (const blockDutyAtEpoch of this.proposers.values()) {
blockDutyAtEpoch.data = blockDutyAtEpoch.data.filter((proposer) => {
return toHexString(proposer.pubkey) !== pubkey;
return toPubkeyHex(proposer.pubkey) !== pubkey;
});
}
}
Expand Down Expand Up @@ -187,7 +186,7 @@ export class BlockDutiesService {
const proposerDuties = res.value();
const {dependentRoot} = res.meta();
const relevantDuties = proposerDuties.filter((duty) => {
const pubkeyHex = toHexString(duty.pubkey);
const pubkeyHex = toPubkeyHex(duty.pubkey);
return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex);
});

Expand Down
Loading
Loading