Skip to content

Commit

Permalink
fix: toPubkeyHex (#7065)
Browse files Browse the repository at this point in the history
* feat: add toPubkeyHex util

* feat: consume toPubkeyHex

* fix: use toPubkeyHex instead of toHex

* chore: remove redundant comments
  • Loading branch information
twoeths authored and philknows committed Sep 11, 2024
1 parent 0cb2560 commit fdf08b4
Show file tree
Hide file tree
Showing 20 changed files with 138 additions and 71 deletions.
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
5 changes: 2 additions & 3 deletions packages/cli/src/cmds/validator/slashingProtection/export.ts
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

0 comments on commit fdf08b4

Please sign in to comment.