Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
baa4209
feat: add GLOAS (Glamsterdam) signing support
usmansaleem May 6, 2026
c2a2e1e
openapi: inline Withdrawal field types in ExecutionPayloadGloas
usmansaleem May 6, 2026
c6db7a7
openapi: bump eth2 spec version to 2.1.0 for Glamsterdam additions
usmansaleem May 6, 2026
68fdaad
Use imports instead of fully-qualified names for non-conflicting types
usmansaleem May 6, 2026
2b426c4
docs: add CHANGELOG entry for GLOAS signing support
usmansaleem May 6, 2026
ec8d5fc
docs: shorten CHANGELOG entry, link PR
usmansaleem May 6, 2026
8b3001d
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem May 6, 2026
8a93a05
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem Aug 25, 2026
699ae2f
Update Glamsterdam (ePBS) signing for latest Teku develop; add BUILDE…
usmansaleem Aug 25, 2026
a0f5d3a
Add --Xnetwork-gloas-fork-epoch and fix dormant Jackson gaps for GLOAS
usmansaleem Aug 25, 2026
0da4d26
docs: drop misleading ValidatorRegistrationV1 analogy for BuilderRequ…
usmansaleem Aug 25, 2026
d5cb7b9
openapi: align BuilderRequestAuth schema ordering with remote-signing…
usmansaleem Aug 25, 2026
c36c245
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem Sep 3, 2026
27431fb
Version Gloas signing types per remote-signing-api#28
usmansaleem Sep 3, 2026
6d19960
Align Gloas BuilderRequestAuth with latest Teku develop and force cac…
usmansaleem Sep 3, 2026
2dab6fe
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem Sep 7, 2026
a8296db
update renovatebot action hash
joshuafernandes Sep 10, 2026
71f975a
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem Sep 13, 2026
5670340
Sync openapi-specs/eth2 with remote-signing-api#28 review fixes
usmansaleem Sep 14, 2026
3fd20e0
Merge remote-tracking branch 'upstream/master' into glamsterdam_changes
usmansaleem Sep 14, 2026
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Upcoming Release
### Features Added
- Azure Key Vault connections are now cached per credential/vault set instead of rebuilt on every key load, reducing bulk-load time. [#1222][PR_1222]
- Updated Glamsterdam (ePBS) signing support to a fork-versioned request format, matching [remote-signing-api#28](https://github.com/ethereum/remote-signing-api/pull/28).

### Bugs Fixed
- Azure Key Vault SECP256K1 signing now uses one official Azure SDK `CryptographyClient` per key instead of REST workaround. [#1222][PR_1222]
Expand All @@ -29,6 +30,11 @@
- Docker images are unchanged — they have shipped Java 25 since 25.12.0.
- Contributors no longer need to install JDK 25 manually. The build now uses a Gradle toolchain (`JavaLanguageVersion.of(25)`) with the foojay resolver, so Gradle will auto-detect a locally installed JDK 25 and download Temurin 25 if none is found. The Gradle daemon itself can run on any JDK supported by Gradle 9 (17+).

### Features Added
- Initial signing support for the upcoming Glamsterdam (GLOAS / ePBS) fork. Subject to change until the next Teku release pins the schemas. PR [#1192][PR_1192].

[PR_1192]: https://github.com/Consensys/web3signer/pull/1192

### Security
- Update base docker image to latest LTS Ubuntu 26.04.

Expand Down
1 change: 1 addition & 0 deletions acceptance-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
testImplementation 'tech.pegasys.teku.internal:serializer'
testImplementation 'tech.pegasys.teku.internal:unsigned'
testImplementation 'tech.pegasys.teku.internal:async'
testImplementation 'tech.pegasys.teku.internal:execution-types'
testImplementation 'io.rest-assured:rest-assured'
testImplementation 'org.web3j:core'
testImplementation 'org.web3j:crypto'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class SignerConfiguration {
private final Optional<Long> denebForkEpoch;
private final Optional<Long> electraForkEpoch;
private final Optional<Long> fuluForkEpoch;
private final Optional<Long> gloasForkEpoch;
private final Optional<String> network;
private final boolean keyManagerApiEnabled;
private Optional<WatermarkRepairParameters> watermarkRepairParameters;
Expand Down Expand Up @@ -123,6 +124,7 @@ public SignerConfiguration(
final Optional<Long> denebForkEpoch,
final Optional<Long> electraForkEpoch,
final Optional<Long> fuluForkEpoch,
final Optional<Long> gloasForkEpoch,
final Optional<String> network,
final boolean keyManagerApiEnabled,
final Optional<WatermarkRepairParameters> watermarkRepairParameters,
Expand Down Expand Up @@ -170,6 +172,7 @@ public SignerConfiguration(
this.denebForkEpoch = denebForkEpoch;
this.electraForkEpoch = electraForkEpoch;
this.fuluForkEpoch = fuluForkEpoch;
this.gloasForkEpoch = gloasForkEpoch;
this.network = network;
this.keyManagerApiEnabled = keyManagerApiEnabled;
this.watermarkRepairParameters = watermarkRepairParameters;
Expand Down Expand Up @@ -333,6 +336,10 @@ public Optional<Long> getFuluForkEpoch() {
return fuluForkEpoch;
}

public Optional<Long> getGloasForkEpoch() {
return gloasForkEpoch;
}

public Optional<String> getNetwork() {
return network;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class SignerConfigurationBuilder {
private Long denebForkEpoch = null;
private Long electraForkEpoch = null;
private Long fuluForkEpoch = null;
private Long gloasForkEpoch = null;
private String network = null;
private boolean keyManagerApiEnabled = false;
private KeystoresParameters keystoresParameters;
Expand Down Expand Up @@ -280,6 +281,11 @@ public SignerConfigurationBuilder withFuluForkEpoch(final long fuluForkEpoch) {
return this;
}

public SignerConfigurationBuilder withGloasForkEpoch(final long gloasForkEpoch) {
this.gloasForkEpoch = gloasForkEpoch;
return this;
}

public SignerConfigurationBuilder withNetwork(final String network) {
this.network = network;
return this;
Expand Down Expand Up @@ -382,6 +388,7 @@ public SignerConfiguration build() {
Optional.ofNullable(denebForkEpoch),
Optional.ofNullable(electraForkEpoch),
Optional.ofNullable(fuluForkEpoch),
Optional.ofNullable(gloasForkEpoch),
Optional.ofNullable(network),
keyManagerApiEnabled,
Optional.ofNullable(watermarkRepairParameters),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,10 @@ private Map<String, Object> createEth2SlashingProtectionArgs() {
yamlConfigMap.put("eth2.Xnetwork-fulu-fork-epoch", signerConfig.getFuluForkEpoch().get());
}

if (signerConfig.getGloasForkEpoch().isPresent()) {
yamlConfigMap.put("eth2.Xnetwork-gloas-fork-epoch", signerConfig.getGloasForkEpoch().get());
}

if (signerConfig.getNetwork().isPresent()) {
yamlConfigMap.put("eth2.network", signerConfig.getNetwork().get());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ private Collection<String> createEth2Args() {
params.add(Long.toString(signerConfig.getFuluForkEpoch().get()));
}

if (signerConfig.getGloasForkEpoch().isPresent()) {
params.add("--Xnetwork-gloas-fork-epoch");
params.add(Long.toString(signerConfig.getGloasForkEpoch().get()));
}

if (signerConfig.getNetwork().isPresent()) {
params.add("--network");
params.add(signerConfig.getNetwork().get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static tech.pegasys.web3signer.core.util.DepositSigningRootUtil.computeDomain;

import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.bytes.Bytes20;
import tech.pegasys.teku.infrastructure.bytes.Bytes4;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.Spec;
Expand All @@ -37,6 +38,7 @@
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.RandaoReveal;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncCommitteeMessage;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ValidatorRegistration;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.VersionedRequest;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AggregateAndProof;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Attestation;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData;
Expand All @@ -47,7 +49,15 @@
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Checkpoint;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Eth1Data;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Fork;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.VoluntaryExit;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.electra.ExecutionRequests;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadGloas;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences;
import tech.pegasys.web3signer.core.util.DepositSigningRootUtil;

import java.util.Random;
Expand Down Expand Up @@ -88,6 +98,12 @@ public class Eth2RequestUtils {
private static final Eth2BlockSigningRequestUtil ALTAIR_BLOCK_UTIL =
new Eth2BlockSigningRequestUtil(SpecMilestone.ALTAIR);

// Gloas Spec
private static final Spec GLOAS_SPEC = TestSpecFactory.createMinimalGloas();
private static final DataStructureUtil GLOAS_DATA_STRUCTURE_UTIL =
new DataStructureUtil(GLOAS_SPEC);
private static final SigningRootUtil GLOAS_SIGNING_ROOT_UTIL = new SigningRootUtil(GLOAS_SPEC);

public static Eth2SigningRequestBody createCannedRequest(final ArtifactType artifactType) {
return switch (artifactType) {
case DEPOSIT -> createDepositRequest();
Expand Down Expand Up @@ -116,6 +132,16 @@ public static Eth2SigningRequestBody createCannedRequest(final ArtifactType arti
createSyncCommitteeContributionAndProofRequest();

case VALIDATOR_REGISTRATION -> createValidatorRegistrationRequest();

case EXECUTION_PAYLOAD_BID -> createExecutionPayloadBidRequest();

case EXECUTION_PAYLOAD_ENVELOPE -> createExecutionPayloadEnvelopeRequest();

case PAYLOAD_ATTESTATION_MESSAGE -> createPayloadAttestationMessageRequest();

case PROPOSER_PREFERENCES -> createProposerPreferencesRequest();

case BUILDER_REQUEST_AUTH -> createBuilderRequestAuthRequest();
};
}

Expand Down Expand Up @@ -416,6 +442,129 @@ private static Eth2SigningRequestBody createValidatorRegistrationRequest() {
.build();
}

public static ForkInfo gloasForkInfo() {
final tech.pegasys.teku.spec.datastructures.state.Fork internalFork =
GLOAS_SPEC.getForkSchedule().getFork(UInt64.ZERO);
final Fork fork =
new Fork(
internalFork.getPreviousVersion(),
internalFork.getCurrentVersion(),
internalFork.getEpoch());
return new ForkInfo(fork, Bytes32.fromHexString(GENESIS_VALIDATORS_ROOT));
}

private static Eth2SigningRequestBody createExecutionPayloadBidRequest() {
final ForkInfo forkInfo = gloasForkInfo();
final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid randomBid =
GLOAS_DATA_STRUCTURE_UTIL.randomExecutionPayloadBid();
final ExecutionPayloadBid bid =
new ExecutionPayloadBid(
randomBid.getParentBlockHash(),
randomBid.getParentBlockRoot(),
randomBid.getBlockHash(),
randomBid.getPrevRandao(),
new Bytes20(randomBid.getFeeRecipient().getWrappedBytes()),
randomBid.getGasLimit(),
randomBid.getBuilderIndex(),
randomBid.getSlot(),
randomBid.getValue(),
randomBid.getExecutionPayment(),
randomBid.getBlobKzgCommitments().stream()
.map(c -> new KZGCommitment(c.getKZGCommitment()))
.toList(),
randomBid.getExecutionRequestsRoot());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignExecutionPayloadBid(
randomBid, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.EXECUTION_PAYLOAD_BID)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withExecutionPayloadBid(new VersionedRequest<>(SpecMilestone.GLOAS, bid))
.build();
}

private static Eth2SigningRequestBody createExecutionPayloadEnvelopeRequest() {
final ForkInfo forkInfo = gloasForkInfo();
final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope
randomEnvelope =
GLOAS_DATA_STRUCTURE_UTIL.randomExecutionPayloadEnvelope(UInt64.valueOf(7));
final ExecutionPayloadEnvelope envelope =
new ExecutionPayloadEnvelope(
new ExecutionPayloadGloas(randomEnvelope.getPayload()),
new ExecutionRequests(randomEnvelope.getExecutionRequests()),
randomEnvelope.getBuilderIndex(),
randomEnvelope.getBeaconBlockRoot(),
randomEnvelope.getParentBeaconBlockRoot());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignExecutionPayloadEnvelope(
randomEnvelope, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.EXECUTION_PAYLOAD_ENVELOPE)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withExecutionPayloadEnvelope(new VersionedRequest<>(SpecMilestone.GLOAS, envelope))
.build();
}

private static Eth2SigningRequestBody createPayloadAttestationMessageRequest() {
final ForkInfo forkInfo = gloasForkInfo();
final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData
randomData = GLOAS_DATA_STRUCTURE_UTIL.randomPayloadAttestationData(UInt64.valueOf(7));
final PayloadAttestationData payloadAttestationData =
new PayloadAttestationData(
randomData.getBeaconBlockRoot(),
randomData.getSlot(),
randomData.isPayloadPresent(),
randomData.isBlobDataAvailable());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignPayloadAttestationData(
randomData, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.PAYLOAD_ATTESTATION_MESSAGE)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withPayloadAttestationMessage(
new VersionedRequest<>(SpecMilestone.GLOAS, payloadAttestationData))
.build();
}

private static Eth2SigningRequestBody createProposerPreferencesRequest() {
final ForkInfo forkInfo = gloasForkInfo();
final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ProposerPreferences
randomPreferences = GLOAS_DATA_STRUCTURE_UTIL.randomProposerPreferences();
final ProposerPreferences proposerPreferences =
new ProposerPreferences(
randomPreferences.getDependentRoot(),
randomPreferences.getProposalSlot(),
randomPreferences.getValidatorIndex(),
randomPreferences.getFeeRecipient(),
randomPreferences.getTargetGasLimit());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignProposerPreferences(
randomPreferences, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.PROPOSER_PREFERENCES)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withProposerPreferences(new VersionedRequest<>(SpecMilestone.GLOAS, proposerPreferences))
.build();
}

private static Eth2SigningRequestBody createBuilderRequestAuthRequest() {
final tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderRequestAuth
randomRequestAuth = GLOAS_DATA_STRUCTURE_UTIL.randomBuilderRequestAuth();
final BuilderRequestAuth builderRequestAuth =
new BuilderRequestAuth(randomRequestAuth.getData().getBytes(), randomRequestAuth.getSlot());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignBuilderRequestAuth(randomRequestAuth);
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.BUILDER_REQUEST_AUTH)
.withSigningRoot(signingRoot)
.withBuilderRequestAuth(new VersionedRequest<>(SpecMilestone.GLOAS, builderRequestAuth))
.build();
}

private static tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair
.ContributionAndProof
getContributionAndProof() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncAggregatorSelectionData;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncCommitteeMessage;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ValidatorRegistration;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.VersionedRequest;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BeaconBlock;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.VoluntaryExit;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.ContributionAndProof;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences;

import org.apache.tuweni.bytes.Bytes;

Expand All @@ -46,6 +52,11 @@ public final class Eth2SigningRequestBodyBuilder {
private SyncAggregatorSelectionData syncAggregatorSelectionData;
private ContributionAndProof contributionAndProof;
private ValidatorRegistration validatorRegistration;
private VersionedRequest<ExecutionPayloadBid> executionPayloadBid;
private VersionedRequest<ExecutionPayloadEnvelope> executionPayloadEnvelope;
private VersionedRequest<PayloadAttestationData> payloadAttestationMessage;
private VersionedRequest<ProposerPreferences> proposerPreferences;
private VersionedRequest<BuilderRequestAuth> builderRequestAuth;

private Eth2SigningRequestBodyBuilder() {}

Expand Down Expand Up @@ -133,6 +144,36 @@ public Eth2SigningRequestBodyBuilder withValidatorRegistration(
return this;
}

public Eth2SigningRequestBodyBuilder withExecutionPayloadBid(
VersionedRequest<ExecutionPayloadBid> executionPayloadBid) {
this.executionPayloadBid = executionPayloadBid;
return this;
}

public Eth2SigningRequestBodyBuilder withExecutionPayloadEnvelope(
VersionedRequest<ExecutionPayloadEnvelope> executionPayloadEnvelope) {
this.executionPayloadEnvelope = executionPayloadEnvelope;
return this;
}

public Eth2SigningRequestBodyBuilder withPayloadAttestationMessage(
VersionedRequest<PayloadAttestationData> payloadAttestationMessage) {
this.payloadAttestationMessage = payloadAttestationMessage;
return this;
}

public Eth2SigningRequestBodyBuilder withProposerPreferences(
VersionedRequest<ProposerPreferences> proposerPreferences) {
this.proposerPreferences = proposerPreferences;
return this;
}

public Eth2SigningRequestBodyBuilder withBuilderRequestAuth(
VersionedRequest<BuilderRequestAuth> builderRequestAuth) {
this.builderRequestAuth = builderRequestAuth;
return this;
}

public Eth2SigningRequestBody build() {
return new Eth2SigningRequestBody(
type,
Expand All @@ -149,6 +190,11 @@ public Eth2SigningRequestBody build() {
syncCommitteeMessage,
syncAggregatorSelectionData,
contributionAndProof,
validatorRegistration);
validatorRegistration,
executionPayloadBid,
executionPayloadEnvelope,
payloadAttestationMessage,
proposerPreferences,
builderRequestAuth);
}
}
Loading
Loading