Skip to content
Draft
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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

---
## 26.4.2
### Features Added
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 @@ -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 Down Expand Up @@ -47,7 +48,14 @@
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.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 +96,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 +130,14 @@ 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();
};
}

Expand Down Expand Up @@ -416,6 +438,112 @@ 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(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());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignExecutionPayloadEnvelope(
randomEnvelope, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.EXECUTION_PAYLOAD_ENVELOPE)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withExecutionPayloadEnvelope(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(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.getProposalSlot(),
randomPreferences.getValidatorIndex(),
randomPreferences.getFeeRecipient(),
randomPreferences.getGasLimit());
final Bytes signingRoot =
GLOAS_SIGNING_ROOT_UTIL.signingRootForSignProposerPreferences(
randomPreferences, forkInfo.asInternalForkInfo());
return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody()
.withType(ArtifactType.PROPOSER_PREFERENCES)
.withSigningRoot(signingRoot)
.withForkInfo(forkInfo)
.withProposerPreferences(proposerPreferences)
.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 @@ -27,6 +27,10 @@
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.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 +50,10 @@ public final class Eth2SigningRequestBodyBuilder {
private SyncAggregatorSelectionData syncAggregatorSelectionData;
private ContributionAndProof contributionAndProof;
private ValidatorRegistration validatorRegistration;
private ExecutionPayloadBid executionPayloadBid;
private ExecutionPayloadEnvelope executionPayloadEnvelope;
private PayloadAttestationData payloadAttestationMessage;
private ProposerPreferences proposerPreferences;

private Eth2SigningRequestBodyBuilder() {}

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

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

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

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

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

public Eth2SigningRequestBody build() {
return new Eth2SigningRequestBody(
type,
Expand All @@ -149,6 +181,10 @@ public Eth2SigningRequestBody build() {
syncCommitteeMessage,
syncAggregatorSelectionData,
contributionAndProof,
validatorRegistration);
validatorRegistration,
executionPayloadBid,
executionPayloadEnvelope,
payloadAttestationMessage,
proposerPreferences);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,11 @@ public void failsIfSigningRootDoesNotMatchSigningData(final ArtifactType artifac
request.syncCommitteeMessage(),
request.syncAggregatorSelectionData(),
request.contributionAndProof(),
request.validatorRegistration());
request.validatorRegistration(),
request.executionPayloadBid(),
request.executionPayloadEnvelope(),
request.payloadAttestationMessage(),
request.proposerPreferences());

final Response response =
signer.eth2Sign(KEY_PAIR.getPublicKey().toString(), requestWithMismatchedSigningRoot);
Expand Down Expand Up @@ -316,7 +320,11 @@ public void ableToSignWithoutSigningRootField(final ContentType acceptableConten
request.syncCommitteeMessage(),
request.syncAggregatorSelectionData(),
request.contributionAndProof(),
request.validatorRegistration());
request.validatorRegistration(),
request.executionPayloadBid(),
request.executionPayloadEnvelope(),
request.payloadAttestationMessage(),
request.proposerPreferences());

final Response response =
signer.eth2Sign(
Expand Down Expand Up @@ -373,6 +381,11 @@ private void setupMinimalWeb3Signer(final ArtifactType artifactType) {
SYNC_COMMITTEE_SELECTION_PROOF,
SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF ->
setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.ALTAIR);
case EXECUTION_PAYLOAD_BID,
EXECUTION_PAYLOAD_ENVELOPE,
PAYLOAD_ATTESTATION_MESSAGE,
PROPOSER_PREFERENCES ->
setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.GLOAS);
default -> setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.PHASE0);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@ public enum ArtifactType {
SYNC_COMMITTEE_MESSAGE,
SYNC_COMMITTEE_SELECTION_PROOF,
SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF,
VALIDATOR_REGISTRATION
VALIDATOR_REGISTRATION,
EXECUTION_PAYLOAD_BID,
EXECUTION_PAYLOAD_ENVELOPE,
PAYLOAD_ATTESTATION_MESSAGE,
PROPOSER_PREFERENCES
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.ContributionAndProof;
import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.SyncCommitteeContribution;
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 tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics;
import tech.pegasys.web3signer.core.util.DepositSigningRootUtil;
import tech.pegasys.web3signer.slashingprotection.SlashingProtection;
Expand Down Expand Up @@ -319,6 +323,36 @@ private Bytes computeSigningRoot(final Eth2SigningRequestBody body) {
return signingRootUtil.signingRootForValidatorRegistration(
validatorRegistration.asInternalValidatorRegistration());
}
case EXECUTION_PAYLOAD_BID -> {
final ExecutionPayloadBid bid = body.executionPayloadBid();
checkArgument(bid != null, "executionPayloadBid is required");
return signingRootUtil.signingRootForSignExecutionPayloadBid(
bid.asInternalExecutionPayloadBid(eth2Spec.atSlot(bid.getSlot())),
body.forkInfo().asInternalForkInfo());
}
case EXECUTION_PAYLOAD_ENVELOPE -> {
final ExecutionPayloadEnvelope envelope = body.executionPayloadEnvelope();
checkArgument(envelope != null, "executionPayloadEnvelope is required");
return signingRootUtil.signingRootForSignExecutionPayloadEnvelope(
envelope.asInternalExecutionPayloadEnvelope(eth2Spec.atSlot(envelope.getSlot())),
body.forkInfo().asInternalForkInfo());
}
case PAYLOAD_ATTESTATION_MESSAGE -> {
final PayloadAttestationData payloadAttestationData = body.payloadAttestationMessage();
checkArgument(payloadAttestationData != null, "payloadAttestationMessage is required");
return signingRootUtil.signingRootForSignPayloadAttestationData(
payloadAttestationData.asInternalPayloadAttestationData(
eth2Spec.atSlot(payloadAttestationData.getSlot())),
body.forkInfo().asInternalForkInfo());
}
case PROPOSER_PREFERENCES -> {
final ProposerPreferences proposerPreferences = body.proposerPreferences();
checkArgument(proposerPreferences != null, "proposerPreferences is required");
return signingRootUtil.signingRootForSignProposerPreferences(
proposerPreferences.asInternalProposerPreferences(
eth2Spec.atSlot(proposerPreferences.getProposalSlot())),
body.forkInfo().asInternalForkInfo());
}
default ->
throw new IllegalStateException("Signing root unimplemented for type " + body.type());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
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.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 com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -38,4 +42,8 @@ public record Eth2SigningRequestBody(
@JsonProperty("sync_aggregator_selection_data")
SyncAggregatorSelectionData syncAggregatorSelectionData,
@JsonProperty("contribution_and_proof") ContributionAndProof contributionAndProof,
@JsonProperty("validator_registration") ValidatorRegistration validatorRegistration) {}
@JsonProperty("validator_registration") ValidatorRegistration validatorRegistration,
@JsonProperty("execution_payload_bid") ExecutionPayloadBid executionPayloadBid,
@JsonProperty("execution_payload_envelope") ExecutionPayloadEnvelope executionPayloadEnvelope,
@JsonProperty("payload_attestation_message") PayloadAttestationData payloadAttestationMessage,
@JsonProperty("proposer_preferences") ProposerPreferences proposerPreferences) {}
Loading
Loading