From ec270da4c32a3b900cb7daeb5cf2e001c7f7ab68 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Sat, 12 Sep 2026 16:54:34 +0100 Subject: [PATCH] fix: align gloas external signer requests with remote signing api --- .../validator/src/services/validatorStore.ts | 2 +- .../src/util/externalSignerClient.ts | 52 +++++++++++++---- .../test/e2e-mainnet/web3signer.test.ts | 56 +++++++++++++++++++ 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index d9cecd7f668e..df6489a5a6a2 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -909,7 +909,7 @@ export class ValidatorStore { }); const signableMessage: SignableMessage = { - type: SignableMessageType.PAYLOAD_ATTESTATION, + type: SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE, data, }; diff --git a/packages/validator/src/util/externalSignerClient.ts b/packages/validator/src/util/externalSignerClient.ts index 9582540b4121..eebc714c0263 100644 --- a/packages/validator/src/util/externalSignerClient.ts +++ b/packages/validator/src/util/externalSignerClient.ts @@ -1,6 +1,6 @@ import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {BeaconConfig} from "@lodestar/config"; -import {ForkName, ForkPreBellatrix, ForkSeq, isForkPostDeneb} from "@lodestar/params"; +import {ForkName, ForkPostGloas, ForkPreBellatrix, ForkSeq, isForkPostDeneb} from "@lodestar/params"; import {blindedOrFullBlockToHeader, computeEpochAtSlot} from "@lodestar/state-transition"; import { AggregateAndProof, @@ -34,7 +34,7 @@ export enum SignableMessageType { SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF = "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF", VALIDATOR_REGISTRATION = "VALIDATOR_REGISTRATION", EXECUTION_PAYLOAD_ENVELOPE = "EXECUTION_PAYLOAD_ENVELOPE", - PAYLOAD_ATTESTATION = "PAYLOAD_ATTESTATION", + PAYLOAD_ATTESTATION_MESSAGE = "PAYLOAD_ATTESTATION_MESSAGE", PROPOSER_PREFERENCES = "PROPOSER_PREFERENCES", BUILDER_REQUEST_AUTH = "BUILDER_REQUEST_AUTH", } @@ -87,7 +87,7 @@ export type SignableMessage = | {type: SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF; data: altair.ContributionAndProof} | {type: SignableMessageType.VALIDATOR_REGISTRATION; data: ValidatorRegistrationV1} | {type: SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE; data: gloas.ExecutionPayloadEnvelope} - | {type: SignableMessageType.PAYLOAD_ATTESTATION; data: gloas.PayloadAttestationData} + | {type: SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE; data: gloas.PayloadAttestationData} | {type: SignableMessageType.PROPOSER_PREFERENCES; data: gloas.ProposerPreferences} | {type: SignableMessageType.BUILDER_REQUEST_AUTH; data: gloas.BuilderRequestAuth}; @@ -105,7 +105,7 @@ const requiresForkInfo: Record = { [SignableMessageType.SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF]: true, [SignableMessageType.VALIDATOR_REGISTRATION]: false, [SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE]: true, - [SignableMessageType.PAYLOAD_ATTESTATION]: true, + [SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE]: true, [SignableMessageType.PROPOSER_PREFERENCES]: true, [SignableMessageType.BUILDER_REQUEST_AUTH]: false, }; @@ -280,17 +280,45 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl case SignableMessageType.VALIDATOR_REGISTRATION: return {validator_registration: ssz.bellatrix.ValidatorRegistrationV1.toJson(payload.data)}; - case SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE: - return {execution_payload_envelope: ssz.gloas.ExecutionPayloadEnvelope.toJson(payload.data)}; + case SignableMessageType.EXECUTION_PAYLOAD_ENVELOPE: { + const slot = payload.data.payload.slotNumber; + return { + execution_payload_envelope: { + version: config.getForkName(slot).toUpperCase(), + data: config.getForkTypes(slot).ExecutionPayloadEnvelope.toJson(payload.data), + }, + }; + } - case SignableMessageType.PAYLOAD_ATTESTATION: - return {payload_attestation: ssz.gloas.PayloadAttestationData.toJson(payload.data)}; + case SignableMessageType.PAYLOAD_ATTESTATION_MESSAGE: { + const slot = payload.data.slot; + return { + payload_attestation_message: { + version: config.getForkName(slot).toUpperCase(), + data: config.getForkTypes(slot).PayloadAttestationData.toJson(payload.data), + }, + }; + } - case SignableMessageType.PROPOSER_PREFERENCES: - return {proposer_preferences: ssz.gloas.ProposerPreferences.toJson(payload.data)}; + case SignableMessageType.PROPOSER_PREFERENCES: { + const slot = payload.data.proposalSlot; + return { + proposer_preferences: { + version: config.getForkName(slot).toUpperCase(), + data: config.getForkTypes(slot).ProposerPreferences.toJson(payload.data), + }, + }; + } - case SignableMessageType.BUILDER_REQUEST_AUTH: - return {builder_request_auth: ssz.gloas.BuilderRequestAuth.toJson(payload.data)}; + case SignableMessageType.BUILDER_REQUEST_AUTH: { + const slot = payload.data.slot; + return { + builder_request_auth: { + version: config.getForkName(slot).toUpperCase(), + data: config.getForkTypes(slot).BuilderRequestAuth.toJson(payload.data), + }, + }; + } } } diff --git a/packages/validator/test/e2e-mainnet/web3signer.test.ts b/packages/validator/test/e2e-mainnet/web3signer.test.ts index 7f8cc112622a..1f2eb6c5f74c 100644 --- a/packages/validator/test/e2e-mainnet/web3signer.test.ts +++ b/packages/validator/test/e2e-mainnet/web3signer.test.ts @@ -149,6 +149,62 @@ describe("web3signer signature test", () => { await assertSameSignature("signVoluntaryExit", pubkeyBytes, validatorIndex, epoch); }); + it("signExecutionPayloadEnvelope", async ({skip}) => { + if (ForkSeq.gloas > externalSigner.supportedForkSeq) { + skip(); + return; + } + + const slot = computeStartSlotAtEpoch(config.GLOAS_FORK_EPOCH); + const envelope = ssz.gloas.ExecutionPayloadEnvelope.defaultValue(); + envelope.payload.slotNumber = slot; + + await assertSameSignature("signExecutionPayloadEnvelope", pubkeyBytes, envelope, slot); + }); + + it("signPayloadAttestation", async ({skip}) => { + if (ForkSeq.gloas > externalSigner.supportedForkSeq) { + skip(); + return; + } + + const slot = computeStartSlotAtEpoch(config.GLOAS_FORK_EPOCH); + const data = ssz.gloas.PayloadAttestationData.defaultValue(); + data.slot = slot; + + await assertSameSignature("signPayloadAttestation", {pubkey: pubkeyBytes, validatorIndex, slot}, data, slot); + }); + + it("signProposerPreferences", async ({skip}) => { + if (ForkSeq.gloas > externalSigner.supportedForkSeq) { + skip(); + return; + } + + const slot = computeStartSlotAtEpoch(config.GLOAS_FORK_EPOCH) + 1; + const dependentRoot = ssz.phase0.BeaconBlockHeader.defaultValue().bodyRoot; + + await assertSameSignature( + "signProposerPreferences", + {pubkey: pubkeyBytes, validatorIndex, slot}, + dependentRoot, + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 30_000_000, + slot - 1 + ); + }); + + it("signBuilderRequestAuth", async ({skip}) => { + if (ForkSeq.gloas > externalSigner.supportedForkSeq) { + skip(); + return; + } + + const slot = computeStartSlotAtEpoch(config.GLOAS_FORK_EPOCH); + + await assertSameSignature("signBuilderRequestAuth", pubkeyBytes, Buffer.from("https://builder.example.org"), slot); + }); + // ValidatorRegistration includes a timestamp so it's possible that web3signer instance and local instance // sign different messages and this test fails. Disabling unless it can be proven deterministic it.skip("signValidatorRegistration", async () => {