Skip to content

Commit c997edc

Browse files
authored
Merge branch 'master' into SingleBlockProviderFulu
2 parents b8b9641 + 749b651 commit c997edc

File tree

14 files changed

+235
-61
lines changed

14 files changed

+235
-61
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
- Added new metrics `beacon_earliest_available_slot` and
1414
`data_column_sidecar_processing_validated_total`.
15+
- Block proposal duties can now be scheduled in advance for fulu.
1516

1617
### Bug Fixes
17-
- Teku may crash when shutting down
18+
- Fixed a storage issue which sometimes caused Teku to crash during shut down.

beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber;
7474
import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager;
7575
import tech.pegasys.teku.spec.Spec;
76+
import tech.pegasys.teku.spec.SpecMilestone;
7677
import tech.pegasys.teku.spec.SpecVersion;
7778
import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation;
7879
import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock;
@@ -316,24 +317,41 @@ public SafeFuture<Optional<ProposerDuties>> getProposerDuties(final UInt64 epoch
316317
return NodeSyncingException.failedFuture();
317318
}
318319
final UInt64 currentEpoch = combinedChainDataClient.getCurrentEpoch();
319-
final UInt64 stateSlot = spec.computeStartSlotAtEpoch(epoch);
320-
LOG.trace(
321-
"Retrieving proposer duties for epoch {}, current epoch {}, state query slot {}",
322-
epoch,
323-
currentEpoch,
324-
stateSlot);
325-
if (epoch.isGreaterThan(combinedChainDataClient.getCurrentEpoch().plus(DUTY_EPOCH_TOLERANCE))) {
320+
final int tolerance = spec.getSpecConfig(epoch).getMinSeedLookahead() + DUTY_EPOCH_TOLERANCE;
321+
LOG.trace("Proposer duty tolerance is {} epochs", tolerance);
322+
if (epoch.isGreaterThan(currentEpoch.plus(tolerance))) {
326323
return SafeFuture.failedFuture(
327324
new IllegalArgumentException(
328325
String.format(
329-
"Proposer duties were requested for a future epoch (current: %s, requested: %s).",
330-
combinedChainDataClient.getCurrentEpoch().toString(), epoch)));
326+
"Proposer duties were requested %s epochs ahead, only 1 epoch in future is supported.",
327+
epoch.minus(currentEpoch).toString())));
331328
}
329+
330+
final UInt64 stateSlot = getStateSlotForProposerDuties(spec, epoch);
331+
LOG.debug(
332+
"Retrieving proposer duties for epoch {}, current epoch {}, state query slot {}",
333+
epoch,
334+
currentEpoch,
335+
stateSlot);
332336
return combinedChainDataClient
333337
.getStateAtSlotExact(stateSlot)
334338
.thenApply(maybeState -> maybeState.map(state -> getProposerDutiesFromState(state, epoch)));
335339
}
336340

341+
// PRE: the distance between dutiesEpoch and currentEpoch is validated
342+
static UInt64 getStateSlotForProposerDuties(final Spec spec, final UInt64 dutiesEpoch) {
343+
if (spec.isMilestoneSupported(SpecMilestone.FULU)) {
344+
final UInt64 fuluActivationEpoch =
345+
spec.getForkSchedule().getFork(SpecMilestone.FULU).getEpoch();
346+
if (dutiesEpoch.minusMinZero(1).isGreaterThanOrEqualTo(fuluActivationEpoch)) {
347+
// on fulu boundary we have no context,
348+
// but after fulu boundary our dependent root is previous epoch
349+
return spec.computeStartSlotAtEpoch(dutiesEpoch.minusMinZero(1));
350+
}
351+
}
352+
return spec.computeStartSlotAtEpoch(dutiesEpoch);
353+
}
354+
337355
@Override
338356
public SafeFuture<Optional<PtcDuties>> getPtcDuties(
339357
final UInt64 epoch, final IntCollection validatorIndices) {
@@ -1018,10 +1036,18 @@ boolean isSyncActive() {
10181036

10191037
private ProposerDuties getProposerDutiesFromState(final BeaconState state, final UInt64 epoch) {
10201038
final List<ProposerDuty> result = getProposalSlotsForEpoch(state, epoch);
1039+
if (spec.atEpoch(epoch).getMilestone().isLessThan(SpecMilestone.FULU)) {
1040+
return new ProposerDuties(
1041+
spec.atEpoch(epoch).getBeaconStateUtil().getCurrentDutyDependentRoot(state),
1042+
result,
1043+
combinedChainDataClient.isChainHeadOptimistic());
1044+
}
1045+
final Bytes32 dependentRoot =
1046+
epoch.isGreaterThanOrEqualTo(spec.getCurrentEpoch(state))
1047+
? spec.atEpoch(epoch).getBeaconStateUtil().getCurrentDutyDependentRoot(state)
1048+
: spec.atEpoch(epoch).getBeaconStateUtil().getPreviousDutyDependentRoot(state);
10211049
return new ProposerDuties(
1022-
spec.atEpoch(epoch).getBeaconStateUtil().getCurrentDutyDependentRoot(state),
1023-
result,
1024-
combinedChainDataClient.isChainHeadOptimistic());
1050+
dependentRoot, result, combinedChainDataClient.isChainHeadOptimistic());
10251051
}
10261052

10271053
private SafeFuture<Optional<BeaconState>> getStateForCommitteeDuties(

beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,15 @@
4949
import java.util.concurrent.ExecutionException;
5050
import java.util.stream.Collectors;
5151
import java.util.stream.IntStream;
52+
import java.util.stream.Stream;
53+
import org.apache.logging.log4j.LogManager;
54+
import org.apache.logging.log4j.Logger;
5255
import org.apache.tuweni.bytes.Bytes32;
5356
import org.junit.jupiter.api.BeforeEach;
5457
import org.junit.jupiter.api.Test;
58+
import org.junit.jupiter.params.ParameterizedTest;
59+
import org.junit.jupiter.params.provider.Arguments;
60+
import org.junit.jupiter.params.provider.MethodSource;
5561
import org.mockito.InOrder;
5662
import tech.pegasys.teku.api.ChainDataProvider;
5763
import tech.pegasys.teku.api.NetworkDataProvider;
@@ -131,7 +137,7 @@
131137
import tech.pegasys.teku.validator.coordinator.publisher.ExecutionPayloadPublisher;
132138

133139
class ValidatorApiHandlerTest {
134-
140+
private static final Logger LOG = LogManager.getLogger();
135141
private static final UInt64 EPOCH = UInt64.valueOf(13);
136142
private static final UInt64 PREVIOUS_EPOCH = EPOCH.minus(ONE);
137143

@@ -373,21 +379,67 @@ public void getProposerDuties_shouldFailWhenNodeIsSyncing() {
373379

374380
@Test
375381
public void getProposerDuties_shouldFailForEpochTooFarAhead() {
376-
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH.minus(2));
382+
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH.minus(3));
377383

378384
final SafeFuture<Optional<ProposerDuties>> result =
379385
validatorApiHandler.getProposerDuties(EPOCH);
380386
assertThat(result).isCompletedExceptionally();
381387
assertThatThrownBy(result::get).hasRootCauseInstanceOf(IllegalArgumentException.class);
382388
}
383389

390+
public static Stream<Arguments> getStateSlotForProposerDutiesTestCases() {
391+
// | EPOCH | START SLOT | END SLOT |
392+
// | 0 | 0 | 7 |
393+
// | 1 | 8 | 15 |
394+
// | 2 | 16 | 23 |
395+
// | 3 | 24 | 31 |
396+
return Stream.of(
397+
Arguments.of(SpecMilestone.PHASE0, 0, 0),
398+
Arguments.of(SpecMilestone.PHASE0, 1, 8),
399+
Arguments.of(SpecMilestone.PHASE0, 2, 16),
400+
Arguments.of(SpecMilestone.PHASE0, 3, 24),
401+
Arguments.of(SpecMilestone.FULU, 0, 0),
402+
Arguments.of(SpecMilestone.FULU, 1, 0),
403+
Arguments.of(SpecMilestone.FULU, 2, 8),
404+
Arguments.of(SpecMilestone.FULU, 3, 16));
405+
}
406+
407+
@ParameterizedTest
408+
@MethodSource("getStateSlotForProposerDutiesTestCases")
409+
public void getStateSlotForProposerDuties(
410+
final SpecMilestone specMilestone, final int requestedEpoch, final int expectedSlot) {
411+
final Spec localSpec = TestSpecFactory.createMinimal(specMilestone);
412+
final UInt64 querySlot =
413+
ValidatorApiHandler.getStateSlotForProposerDuties(
414+
localSpec, UInt64.valueOf(requestedEpoch));
415+
416+
assertThat(querySlot.intValue()).isEqualTo(expectedSlot);
417+
}
418+
384419
@Test
385-
public void getProposerDuties_shouldReturnDutiesForCurrentEpoch() {
420+
public void getProposerDuties_shouldReturnDutiesForNextEpoch() {
386421
final BeaconState state = createStateWithActiveValidators(epochStartSlot);
387422
when(chainDataClient.getStateAtSlotExact(epochStartSlot))
388423
.thenReturn(completedFuture(Optional.of(state)));
389424
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH);
390425

426+
final SafeFuture<Optional<ProposerDuties>> result =
427+
validatorApiHandler.getProposerDuties(EPOCH.increment());
428+
final ProposerDuties duties = assertCompletedSuccessfully(result).orElseThrow();
429+
assertThat(duties.getDuties().size()).isEqualTo(spec.slotsPerEpoch(EPOCH.increment()));
430+
assertThat(duties.getDependentRoot()).isEqualTo(spec.getCurrentDutyDependentRoot(state));
431+
}
432+
433+
@Test
434+
public void getProposerDuties_shouldReturnDutiesForCurrentEpoch() {
435+
final UInt64 previousEpochStartSlot =
436+
epochStartSlot.minus(spec.getGenesisSpecConfig().getSlotsPerEpoch());
437+
final BeaconState state = createStateWithActiveValidators(previousEpochStartSlot);
438+
439+
when(chainDataClient.getStateAtSlotExact(previousEpochStartSlot))
440+
.thenReturn(completedFuture(Optional.of(state)));
441+
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH);
442+
391443
final SafeFuture<Optional<ProposerDuties>> result =
392444
validatorApiHandler.getProposerDuties(EPOCH);
393445
final ProposerDuties duties = assertCompletedSuccessfully(result).orElseThrow();
@@ -397,10 +449,17 @@ public void getProposerDuties_shouldReturnDutiesForCurrentEpoch() {
397449

398450
@Test
399451
public void getProposerDuties_shouldAllowOneEpochTolerance() {
452+
final UInt64 epoch = EPOCH.minus(1);
400453
final BeaconState state = createStateWithActiveValidators(epochStartSlot);
401-
when(chainDataClient.getStateAtSlotExact(epochStartSlot))
454+
final UInt64 querySlot = spec.computeStartSlotAtEpoch(epoch);
455+
LOG.debug(
456+
"Epoch Start slot {}, Test Epoch {}, expect query slot {}",
457+
epochStartSlot,
458+
epoch,
459+
querySlot);
460+
when(chainDataClient.getStateAtSlotExact(querySlot))
402461
.thenReturn(completedFuture(Optional.of(state)));
403-
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH.minus(1));
462+
when(chainDataClient.getCurrentEpoch()).thenReturn(epoch);
404463

405464
final SafeFuture<Optional<ProposerDuties>> result =
406465
validatorApiHandler.getProposerDuties(EPOCH);
@@ -410,10 +469,12 @@ public void getProposerDuties_shouldAllowOneEpochTolerance() {
410469

411470
@Test
412471
void getProposerDuties_shouldReturnDutiesInOrder() {
413-
final BeaconState state = createStateWithActiveValidators(epochStartSlot);
414-
when(chainDataClient.getStateAtSlotExact(epochStartSlot))
472+
final UInt64 previousEpochStartSlot =
473+
epochStartSlot.minus(spec.getGenesisSpecConfig().getSlotsPerEpoch());
474+
final BeaconState state = createStateWithActiveValidators(previousEpochStartSlot);
475+
when(chainDataClient.getStateAtSlotExact(previousEpochStartSlot))
415476
.thenReturn(completedFuture(Optional.of(state)));
416-
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH.minus(1));
477+
when(chainDataClient.getCurrentEpoch()).thenReturn(EPOCH);
417478

418479
final SafeFuture<Optional<ProposerDuties>> result =
419480
validatorApiHandler.getProposerDuties(EPOCH);

data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/validator/GetProposerDutiesIntegrationTest.java

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@
3333

3434
public class GetProposerDutiesIntegrationTest extends AbstractDataBackedRestAPIIntegrationTest {
3535

36-
// epoch | 0 | 1
37-
// slot | 1 2 3 4 5 6 7 | 8
38-
// query | | ^
39-
// dep | D |
40-
// EXPECT epoch 1 duties with dependent root slot 7
4136
private static final Logger LOG = LogManager.getLogger();
4237

4338
@Test
@@ -52,13 +47,13 @@ void shouldReturnCorrectDependentRootPreFulu() throws IOException {
5247
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getParentRoot());
5348
}
5449

55-
// epoch | 0 | 1
50+
// epoch | 0 |<1>
5651
// slot | 1 2 3 4 5 6 7 |
5752
// query | ^ |
5853
// dep | D |
59-
// EXPECT - should be able to query for the next epoch, may not be stable if its too early.
54+
// EXPECT - should be able to query for the next epoch, may not be stable if it's too early.
6055
@Test
61-
void shouldReturnNextEpochDutiesBeforeEpochTransition() throws IOException {
56+
void shouldReturnNextEpochDutiesFutureEpochPreFulu() throws IOException {
6257
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.ALTAIR);
6358
final List<SignedBlockAndState> chain = createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7);
6459
when(syncService.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
@@ -70,43 +65,67 @@ void shouldReturnNextEpochDutiesBeforeEpochTransition() throws IOException {
7065
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getRoot());
7166
}
7267

73-
// epoch | 0 | 1 | 2
74-
// slot | 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16
75-
// query | | | ^
76-
// dep | | D |
77-
// EXPECT - expect epoch 2 duties with dependent root slot 15
68+
// epoch GENESIS | 0 |<1>
69+
// slot 0 | 1 2 3 4 5 6 7 |
70+
// query | ^ |
71+
// dep D | |
72+
// EXPECT epoch 1 duties with dependent root slot 0 (GENESIS)
7873
@Test
79-
void shouldReturnCorrectDependentRootPreFuluExtraSlots() throws IOException {
80-
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.BELLATRIX);
81-
final List<SignedBlockAndState> chain =
82-
createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
74+
void shouldReturnNextEpochDutiesFutureEpochPostFulu() throws IOException {
75+
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.FULU);
76+
final List<SignedBlockAndState> chain = createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7);
8377
when(syncService.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
8478
when(syncStateProvider.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
79+
logChainData(chain);
80+
final Response response = getProposerDuties("1");
81+
final String responseBody = response.body().string();
82+
assertThat(response.code()).isEqualTo(200);
83+
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getFirst().getParentRoot());
84+
}
8585

86+
// epoch GENESIS | 0 |<1>
87+
// slot 0 | 1 2 3 4 5 6 7 | 8
88+
// query | | ^
89+
// dep D | |
90+
// EXPECT epoch 1 duties with dependent root slot 0 (GENESIS)
91+
@Test
92+
void shouldReturnCorrectDependentRootPostFulu() throws IOException {
93+
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.FULU);
94+
final List<SignedBlockAndState> chain = createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7, 8);
95+
when(syncService.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
96+
when(syncStateProvider.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
8697
logChainData(chain);
87-
final Response response = getProposerDuties("2");
98+
final Response response = getProposerDuties("1");
8899
final String responseBody = response.body().string();
89100
assertThat(response.code()).isEqualTo(200);
90-
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getParentRoot());
101+
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getFirst().getParentRoot());
91102
}
92103

93-
// epoch | 0 | 1 | 2
104+
// epoch | 0 | 1 | <2>
94105
// slot | 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16
95106
// query | | | ^
96107
// dep | | D |
97108
// EXPECT - expect epoch 2 duties with dependent root slot 15
98109
@Test
99-
void shouldReturnCorrectDependentRootPostFulu() throws IOException {
100-
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.FULU);
101-
final List<SignedBlockAndState> chain = createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7, 8);
110+
void shouldReturnCorrectDependentRootPreFuluExtraSlots() throws IOException {
111+
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.BELLATRIX);
112+
final List<SignedBlockAndState> chain =
113+
createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
102114
when(syncService.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
103115
when(syncStateProvider.getCurrentSyncState()).thenReturn(SyncState.IN_SYNC);
104-
final Response response = getProposerDuties("1");
116+
117+
logChainData(chain);
118+
final Response response = getProposerDuties("2");
105119
final String responseBody = response.body().string();
106120
assertThat(response.code()).isEqualTo(200);
107121
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getParentRoot());
108122
}
109123

124+
// epoch GENESIS | 0 |<1> | 2
125+
// slot 0 | 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16
126+
// query | | | ^
127+
// dep D | | |
128+
// EXPECT - expect epoch 1 duties with dependent root slot 0 (GENESIS)
110129
@Test
111130
void shouldReturnCorrectDependentRootPostFuluExtraSlots() throws IOException {
112131
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.FULU);
@@ -118,16 +137,16 @@ void shouldReturnCorrectDependentRootPostFuluExtraSlots() throws IOException {
118137
final Response response = getProposerDuties("1");
119138
final String responseBody = response.body().string();
120139
assertThat(response.code()).isEqualTo(200);
121-
assertThat(dependentRoot(responseBody)).isEqualTo(chain.get(6).getRoot());
140+
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getFirst().getParentRoot());
122141
}
123142

124-
// epoch | 0 | 1 | 2
143+
// epoch | 0 | 1 | <2>
125144
// slot | 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 |
126145
// query | | ^ |
127-
// dep | | D |
146+
// dep | D | |
128147
// EXPECT - expect epoch 2 duties with dependent root slot 15
129148
@Test
130-
void shouldReturnCorrectDependentRootPostFuluExtraSlots2() throws IOException {
149+
void shouldReturnCorrectDependentRootPostFuluExtraSlotsFutureEpoch() throws IOException {
131150
startRestApiAtGenesisWithValidatorApiHandler(SpecMilestone.FULU);
132151
final List<SignedBlockAndState> chain =
133152
createBlocksAtSlots(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
@@ -138,13 +157,13 @@ void shouldReturnCorrectDependentRootPostFuluExtraSlots2() throws IOException {
138157
final Response response = getProposerDuties("2");
139158
final String responseBody = response.body().string();
140159
assertThat(response.code()).isEqualTo(200);
141-
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getRoot());
160+
assertThat(dependentRoot(responseBody)).isEqualTo(chain.get(6).getRoot());
142161
}
143162

144-
// epoch | 0 | 1 | 2
163+
// epoch | 0 | 1 | <2>
145164
// slot | 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 | 16
146165
// query | | | ^
147-
// dep | | D |
166+
// dep | D | |
148167
// EXPECT - expect epoch 2 duties with dependent root slot 15
149168
@Test
150169
void shouldReturnCorrectDependentRootSecondEpoch() throws IOException {
@@ -157,7 +176,7 @@ void shouldReturnCorrectDependentRootSecondEpoch() throws IOException {
157176
final Response response = getProposerDuties("2");
158177
final String responseBody = response.body().string();
159178
assertThat(response.code()).isEqualTo(200);
160-
assertThat(dependentRoot(responseBody)).isEqualTo(chain.getLast().getParentRoot());
179+
assertThat(dependentRoot(responseBody)).isEqualTo(chain.get(6).getRoot());
161180
}
162181

163182
final Bytes32 dependentRoot(final String responseBody) {
@@ -174,12 +193,14 @@ private Response getProposerDuties(final String epoch) throws IOException {
174193
}
175194

176195
private void logChainData(final List<SignedBlockAndState> chain) {
196+
LOG.info(
197+
"Genesis root: {}",
198+
recentChainData.getGenesisData().orElseThrow().getGenesisValidatorsRoot());
177199
chain.forEach(
178200
signedBlockAndState ->
179201
LOG.info(
180-
"SLOT: {}, root: {}, ROOT: {}",
202+
"slot: {}, root: {}",
181203
signedBlockAndState.getSlot(),
182-
signedBlockAndState.getRoot(),
183-
signedBlockAndState.getBlock().getRoot()));
204+
signedBlockAndState.getRoot()));
184205
}
185206
}

0 commit comments

Comments
 (0)