diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a654ced81..5e55eac92c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,5 +18,6 @@ For information on changes in released versions of Teku, see the [releases page] ### Additions and Improvements - Set `User-Agent` header to "teku/v" (e.g. teku/v23.4.0) when making builder bid requests to help builders identify clients and versions. Use `--builder-set-user-agent-header=false` to disable. - Included more context when a request to an external signer fails. + - Added `/eth/v1/beacon/rewards/blocks/{block_id}` rest api endpoint. ### Bug Fixes diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_rewards_blocks_{block_id}.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_rewards_blocks_{block_id}.json index 127803df1eb..3e47938b67e 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_rewards_blocks_{block_id}.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_rewards_blocks_{block_id}.json @@ -1,6 +1,6 @@ { "get" : { - "tags" : [ "Beacon", "Rewards" ], + "tags" : [ "Beacon", "Rewards", "Experimental" ], "operationId" : "getBlockRewards", "summary" : "Get Block Rewards", "description" : "Retrieve block reward info for a single block.", @@ -45,16 +45,6 @@ } } }, - "501" : { - "description" : "Not implemented", - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/HttpErrorResponse" - } - } - } - }, "400" : { "description" : "The request could not be processed, check the response for more information.", "content" : { diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockRewards.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockRewards.json index ec47eeabb2e..1efb0a0b0aa 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockRewards.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockRewards.json @@ -21,9 +21,9 @@ }, "total" : { "type" : "string", - "description" : "unsigned 64 bit integer", + "description" : "long string", "example" : "1", - "format" : "uint64" + "format" : "long" }, "attestations" : { "type" : "string", @@ -33,21 +33,21 @@ }, "sync_aggregate" : { "type" : "string", - "description" : "unsigned 64 bit integer", + "description" : "long string", "example" : "1", - "format" : "uint64" + "format" : "long" }, "proposer_slashings" : { "type" : "string", - "description" : "unsigned 64 bit integer", + "description" : "long string", "example" : "1", - "format" : "uint64" + "format" : "long" }, "attester_slashings" : { "type" : "string", - "description" : "unsigned 64 bit integer", + "description" : "long string", "example" : "1", - "format" : "uint64" + "format" : "long" } } } diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java index 3018322e8ed..dd7ea0aece2 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java @@ -265,7 +265,7 @@ private static RestApi create( .endpoint(new GetVersion()) // Rewards Handlers .endpoint(new GetSyncCommitteeRewards(dataProvider)) - .endpoint(new GetBlockRewards()) + .endpoint(new GetBlockRewards(dataProvider)) .endpoint(new GetAttestationRewards()) // Validator Handlers .endpoint(new PostAttesterDuties(dataProvider)) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewards.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewards.java index cf87aedad00..d4bee096a28 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewards.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewards.java @@ -18,15 +18,18 @@ import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EXECUTION_OPTIMISTIC; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.FINALIZED; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_BEACON; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_EXPERIMENTAL; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_REWARDS; import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.BOOLEAN_TYPE; import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.LONG_TYPE; import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.UINT64_TYPE; import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.commons.lang3.NotImplementedException; +import tech.pegasys.teku.api.ChainDataProvider; +import tech.pegasys.teku.api.DataProvider; import tech.pegasys.teku.api.migrated.BlockRewardData; import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; +import tech.pegasys.teku.infrastructure.restapi.endpoints.AsyncApiResponse; import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata; import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint; import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest; @@ -34,15 +37,16 @@ public class GetBlockRewards extends RestApiEndpoint { public static final String ROUTE = "/eth/v1/beacon/rewards/blocks/{block_id}"; + private final ChainDataProvider chainDataProvider; private static final SerializableTypeDefinition DATA_TYPE = SerializableTypeDefinition.object(BlockRewardData.class) .withField("proposer_index", UINT64_TYPE, BlockRewardData::getProposerIndex) - .withField("total", UINT64_TYPE, BlockRewardData::getTotal) + .withField("total", LONG_TYPE, BlockRewardData::getTotal) .withField("attestations", LONG_TYPE, BlockRewardData::getAttestations) - .withField("sync_aggregate", UINT64_TYPE, BlockRewardData::getSyncAggregate) - .withField("proposer_slashings", UINT64_TYPE, BlockRewardData::getProposerSlashings) - .withField("attester_slashings", UINT64_TYPE, BlockRewardData::getAttesterSlashings) + .withField("sync_aggregate", LONG_TYPE, BlockRewardData::getSyncAggregate) + .withField("proposer_slashings", LONG_TYPE, BlockRewardData::getProposerSlashings) + .withField("attester_slashings", LONG_TYPE, BlockRewardData::getAttesterSlashings) .build(); private static final SerializableTypeDefinition> @@ -55,23 +59,34 @@ public class GetBlockRewards extends RestApiEndpoint { .withField("data", DATA_TYPE, ObjectAndMetaData::getData) .build(); - public GetBlockRewards() { + public GetBlockRewards(final DataProvider dataProvider) { + this(dataProvider.getChainDataProvider()); + } + + public GetBlockRewards(final ChainDataProvider chainDataProvider) { super( EndpointMetadata.get(ROUTE) .operationId("getBlockRewards") .summary("Get Block Rewards") .description("Retrieve block reward info for a single block.") - .tags(TAG_BEACON, TAG_REWARDS) + .tags(TAG_BEACON, TAG_REWARDS, TAG_EXPERIMENTAL) .pathParam(PARAMETER_BLOCK_ID) .response(SC_OK, "Request successful", RESPONSE_TYPE) .withNotFoundResponse() .withInternalErrorResponse() - .withNotImplementedResponse() .build()); + this.chainDataProvider = chainDataProvider; } @Override public void handleRequest(RestApiRequest request) throws JsonProcessingException { - throw new NotImplementedException(); + request.respondAsync( + chainDataProvider + .getBlockRewardsFromBlockId(request.getPathParameter(PARAMETER_BLOCK_ID)) + .thenApply( + result -> + result + .map(AsyncApiResponse::respondOk) + .orElse(AsyncApiResponse.respondNotFound()))); } } diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewardsTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewardsTest.java index a00709e0282..fa0e70c93d7 100644 --- a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewardsTest.java +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/GetBlockRewardsTest.java @@ -14,8 +14,10 @@ package tech.pegasys.teku.beaconrestapi.handlers.v1.rewards; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_IMPLEMENTED; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; @@ -23,11 +25,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.io.Resources; import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.assertj.core.api.Assertions; import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.api.migrated.BlockRewardData; import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest; +import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.http.HttpStatusCodes; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.SpecMilestone; @@ -35,19 +41,28 @@ public class GetBlockRewardsTest extends AbstractMigratedBeaconHandlerTest { private final BlockRewardData data = - new BlockRewardData( - UInt64.valueOf(123), - UInt64.valueOf(123), - 123L, - UInt64.valueOf(123), - UInt64.valueOf(123), - UInt64.valueOf(123)); + new BlockRewardData(UInt64.valueOf(123), 283L, 672L, 982L, 198L); private final ObjectAndMetaData blockRewardsResult = new ObjectAndMetaData<>(data, SpecMilestone.ALTAIR, false, true, true); @BeforeEach void setUp() { - setHandler(new GetBlockRewards()); + setHandler(new GetBlockRewards(chainDataProvider)); + request.setPathParameter("block_id", "head"); + } + + @Test + void shouldReturnBlockAndRewardDataInformation() + throws JsonProcessingException, ExecutionException, InterruptedException { + when(chainDataProvider.getBlockRewardsFromBlockId(any())) + .thenReturn(SafeFuture.completedFuture(Optional.of(blockRewardsResult))); + handler.handleRequest(request); + + final ObjectAndMetaData output = + chainDataProvider.getBlockRewardsFromBlockId("head").get().orElseThrow(); + + Assertions.assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(output); } @Test @@ -65,11 +80,6 @@ void metadata_shouldHandle500() throws JsonProcessingException { verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR); } - @Test - void metadata_shouldHandle501() throws JsonProcessingException { - verifyMetadataErrorResponse(handler, SC_NOT_IMPLEMENTED); - } - @Test void metadata_shouldHandle200() throws IOException { final String data = getResponseStringFromMetadata(handler, SC_OK, blockRewardsResult); diff --git a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/blockRewardsData.json b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/blockRewardsData.json index 2782dff552b..6269afc20df 100644 --- a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/blockRewardsData.json +++ b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v1/rewards/blockRewardsData.json @@ -1 +1 @@ -{"execution_optimistic":false,"finalized":true,"data":{"proposer_index":"123","total":"123","attestations":"123","sync_aggregate":"123","proposer_slashings":"123","attester_slashings":"123"}} \ No newline at end of file +{"execution_optimistic":false,"finalized":true,"data":{"proposer_index":"123","total":"2135","attestations":"283","sync_aggregate":"672","proposer_slashings":"982","attester_slashings":"198"}} \ No newline at end of file diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java index f162e469aaf..94c534e9ca7 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java @@ -19,6 +19,8 @@ import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; +import static tech.pegasys.teku.spec.constants.IncentivizationWeights.PROPOSER_WEIGHT; +import static tech.pegasys.teku.spec.constants.IncentivizationWeights.WEIGHT_DENOMINATOR; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; @@ -42,6 +44,7 @@ import tech.pegasys.teku.api.exceptions.BadRequestException; import tech.pegasys.teku.api.exceptions.ServiceUnavailableException; import tech.pegasys.teku.api.migrated.BlockHeadersResponse; +import tech.pegasys.teku.api.migrated.BlockRewardData; import tech.pegasys.teku.api.migrated.StateSyncCommitteesData; import tech.pegasys.teku.api.migrated.StateValidatorBalanceData; import tech.pegasys.teku.api.migrated.StateValidatorData; @@ -61,7 +64,6 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -80,8 +82,6 @@ import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -import tech.pegasys.teku.spec.logic.common.statetransition.epoch.EpochProcessor; -import tech.pegasys.teku.spec.logic.common.statetransition.epoch.RewardAndPenaltyDeltas; import tech.pegasys.teku.spec.logic.common.statetransition.epoch.status.ValidatorStatuses; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; @@ -684,21 +684,76 @@ protected SyncCommitteeRewardData calculateRewards( return data; } + public SafeFuture>> getBlockRewardsFromBlockId( + final String blockId) { + return getBlockAndMetaData(blockId) + .thenCompose( + result -> { + if (result.isEmpty() || result.get().getData().getBeaconBlock().isEmpty()) { + return SafeFuture.completedFuture(Optional.empty()); + } + final BlockAndMetaData blockAndMetaData = result.get(); + final BeaconBlock block = blockAndMetaData.getData().getBeaconBlock().get(); + + return combinedChainDataClient + .getStateByBlockRoot(block.getRoot()) + .thenApply( + maybeState -> + maybeState.map(state -> getBlockRewardData(blockAndMetaData, state))); + }); + } + @VisibleForTesting - protected int calculateProposerSyncAggregateBlockRewards( - UInt64 proposerReward, SyncAggregate aggregate) { - final SszBitvector syncCommitteeBits = aggregate.getSyncCommitteeBits(); - int total = 0; - for (int i = 0; i < syncCommitteeBits.size(); i++) { - if (syncCommitteeBits.getBit(i)) { - total += proposerReward.intValue(); - } + protected ObjectAndMetaData getBlockRewardData( + final BlockAndMetaData blockAndMetaData, + final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state) { + final BeaconBlock block = blockAndMetaData.getData().getMessage(); + if (!spec.atSlot(block.getSlot()).getMilestone().isGreaterThanOrEqualTo(SpecMilestone.ALTAIR)) { + throw new BadRequestException( + "Slot " + + block.getSlot() + + " is pre altair, and no sync committee information is available"); } - return total; + + final UInt64 participantReward = spec.getSyncCommitteeParticipantReward(state); + final long proposerReward = + participantReward + .times(PROPOSER_WEIGHT) + .dividedBy(WEIGHT_DENOMINATOR.minus(PROPOSER_WEIGHT)) + .longValue(); + return blockAndMetaData.map(__ -> calculateBlockRewards(proposerReward, block, state)); + } + + private BlockRewardData calculateBlockRewards( + final long proposerReward, + final BeaconBlock block, + final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state) { + final SyncAggregate aggregate = block.getBody().getOptionalSyncAggregate().orElseThrow(); + + final UInt64 proposerIndex = block.getProposerIndex(); + final long attestationsBlockRewards = calculateAttestationRewards(); + final long syncAggregateBlockRewards = + calculateProposerSyncAggregateBlockRewards(proposerReward, aggregate); + final long proposerSlashingsBlockRewards = calculateProposerSlashingsRewards(block, state); + final long attesterSlashingsBlockRewards = calculateAttesterSlashingsRewards(block, state); + + return new BlockRewardData( + proposerIndex, + attestationsBlockRewards, + syncAggregateBlockRewards, + proposerSlashingsBlockRewards, + attesterSlashingsBlockRewards); } @VisibleForTesting - protected UInt64 calculateProposerSlashingsRewards( + protected long calculateProposerSyncAggregateBlockRewards( + long proposerReward, SyncAggregate aggregate) { + final SszBitvector syncCommitteeBits = aggregate.getSyncCommitteeBits(); + return proposerReward * syncCommitteeBits.getBitCount(); + } + + @VisibleForTesting + protected long calculateProposerSlashingsRewards( final BeaconBlock beaconBlock, final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state) { final SszList proposerSlashings = @@ -707,7 +762,7 @@ protected UInt64 calculateProposerSlashingsRewards( final UInt64 epoch = spec.computeEpochAtSlot(state.getSlot()); final SpecConfig specConfig = spec.getSpecConfig(epoch); - UInt64 proposerSlashingsRewards = ZERO; + long proposerSlashingsRewards = 0; for (ProposerSlashing slashing : proposerSlashings) { final int slashedIndex = slashing.getHeader1().getMessage().getProposerIndex().intValue(); proposerSlashingsRewards = @@ -718,7 +773,7 @@ protected UInt64 calculateProposerSlashingsRewards( } @VisibleForTesting - protected UInt64 calculateAttesterSlashingsRewards( + protected long calculateAttesterSlashingsRewards( final BeaconBlock beaconBlock, final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state) { final SszList attesterSlashings = @@ -727,7 +782,7 @@ protected UInt64 calculateAttesterSlashingsRewards( final UInt64 epoch = spec.computeEpochAtSlot(state.getSlot()); final SpecConfig specConfig = spec.getSpecConfig(epoch); - UInt64 attesterSlashingsRewards = ZERO; + long attesterSlashingsRewards = 0; for (AttesterSlashing slashing : attesterSlashings) { for (final UInt64 index : slashing.getIntersectingValidatorIndices()) { attesterSlashingsRewards = @@ -738,41 +793,23 @@ protected UInt64 calculateAttesterSlashingsRewards( return attesterSlashingsRewards; } - private UInt64 calculateSlashingRewards( + private long calculateSlashingRewards( final SpecConfig specConfig, final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state, final int slashedIndex, - final UInt64 currentRewards) { + final long currentRewards) { final Validator validator = state.getValidators().get(slashedIndex); final UInt64 whistleblowerReward = validator.getEffectiveBalance().dividedBy(specConfig.getWhistleblowerRewardQuotient()); final UInt64 proposerReward = whistleblowerReward.dividedBy(specConfig.getProposerRewardQuotient()); - return currentRewards.plus(proposerReward).plus(whistleblowerReward.minus(proposerReward)); + final UInt64 rewardsAdditions = proposerReward.plus(whistleblowerReward.minus(proposerReward)); + return currentRewards + rewardsAdditions.longValue(); } @VisibleForTesting - protected long calculateAttestationRewards( - final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state) { - final SpecMilestone specMilestone = getMilestoneAtSlot(state.getSlot()); - final SpecVersion specVersion = spec.forMilestone(specMilestone); - final EpochProcessor epochProcessor = specVersion.getEpochProcessor(); - final ValidatorStatuses validatorStatuses = - specVersion.getValidatorStatusFactory().createValidatorStatuses(state); - final RewardAndPenaltyDeltas rewardAndPenaltyDeltas = - epochProcessor.getRewardAndPenaltyDeltas(state, validatorStatuses); - - long rewards = 0; - for (int i = 0; i < state.getValidators().size(); i++) { - final RewardAndPenaltyDeltas.RewardAndPenalty rewardAndPenalty = - rewardAndPenaltyDeltas.getDelta(i); - rewards = - rewards - + rewardAndPenalty.getReward().longValue() - - rewardAndPenalty.getPenalty().longValue(); - } - - return rewards; + protected long calculateAttestationRewards() { + return 0L; } public SpecMilestone getMilestoneAtSlot(final UInt64 slot) { diff --git a/data/provider/src/test/java/tech/pegasys/teku/api/ChainDataProviderTest.java b/data/provider/src/test/java/tech/pegasys/teku/api/ChainDataProviderTest.java index a12b47a0fbe..980d6c75139 100644 --- a/data/provider/src/test/java/tech/pegasys/teku/api/ChainDataProviderTest.java +++ b/data/provider/src/test/java/tech/pegasys/teku/api/ChainDataProviderTest.java @@ -24,11 +24,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture; import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.assertThatSafeFuture; import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin; +import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; import java.util.ArrayList; @@ -48,6 +50,7 @@ import tech.pegasys.teku.api.exceptions.BadRequestException; import tech.pegasys.teku.api.exceptions.ServiceUnavailableException; import tech.pegasys.teku.api.migrated.BlockHeadersResponse; +import tech.pegasys.teku.api.migrated.BlockRewardData; import tech.pegasys.teku.api.migrated.StateSyncCommitteesData; import tech.pegasys.teku.api.migrated.SyncCommitteeRewardData; import tech.pegasys.teku.api.response.v1.beacon.GenesisData; @@ -557,32 +560,71 @@ public void calculateRewards_shouldGetData() { .containsExactlyInAnyOrder(Map.entry(2, -1 * reward), Map.entry(4, reward)); } + @Test + public void getBlockRewardsFromBlockId_shouldCheckStateAndBlockAreHandled() + throws ExecutionException, InterruptedException { + final ChainDataProvider provider = setupAltairState(); + when(mockCombinedChainDataClient.getStateByBlockRoot(any())) + .thenReturn(SafeFuture.completedFuture(Optional.empty())); + + final SafeFuture>> future = + provider.getBlockRewardsFromBlockId("head"); + + final Optional blockAndMetadata = provider.getBlockAndMetaData("head").get(); + assertThat(blockAndMetadata).isNotEmpty(); + verify(mockCombinedChainDataClient, times(1)) + .getStateByBlockRoot(blockAndMetadata.get().getData().getRoot()); + SafeFutureAssert.assertThatSafeFuture(future).isCompletedWithEmptyOptional(); + } + + @Test + public void getBlockRewardData_shouldGetData() { + final Spec spec = TestSpecFactory.createMinimalAltair(); + final DataStructureUtil data = new DataStructureUtil(spec); + final ChainDataProvider provider = setupAltairState(); + + final SignedBlockAndState signedBlockAndState = + data.randomSignedBlockAndStateWithValidatorLogic(100); + final BlockAndMetaData blockAndMetaData = + new BlockAndMetaData( + signedBlockAndState.getBlock(), SpecMilestone.ALTAIR, true, false, true); + + final ObjectAndMetaData result = + provider.getBlockRewardData(blockAndMetaData, signedBlockAndState.getState()); + + final BlockRewardData blockRewardData = + new BlockRewardData( + signedBlockAndState.getBlock().getProposerIndex(), 0L, 35L, 62500000L, 62500000L); + final ObjectAndMetaData expectedOutput = + new ObjectAndMetaData<>(blockRewardData, SpecMilestone.ALTAIR, true, false, true); + assertThat(result).isEqualTo(expectedOutput); + } + @Test public void calculateProposerSyncAggregateBlockRewards_manySyncAggregateIndices() { - final UInt64 reward = UInt64.valueOf(1234); + final long reward = 1234L; final Spec spec = TestSpecFactory.createMinimalAltair(); final DataStructureUtil data = new DataStructureUtil(spec); final ChainDataProvider provider = setupAltairState(); final int[] participantIndices = new int[] {0, 3, 4, 7, 16, 17, 20, 23, 25, 26, 29, 30}; final SyncAggregate syncAggregate = data.randomSyncAggregate(participantIndices); - final int syncAggregateBlockRewards = + final long syncAggregateBlockRewards = provider.calculateProposerSyncAggregateBlockRewards(reward, syncAggregate); - assertThat(syncAggregateBlockRewards) - .isEqualTo(reward.times(participantIndices.length).intValue()); + assertThat(syncAggregateBlockRewards).isEqualTo(reward * participantIndices.length); } @Test public void calculateProposerSyncAggregateBlockRewards_emptySyncAggregate() { - final UInt64 reward = UInt64.valueOf(1234); + final long reward = 1234L; final Spec spec = TestSpecFactory.createMinimalAltair(); final DataStructureUtil data = new DataStructureUtil(spec); final ChainDataProvider provider = setupAltairState(); final SyncAggregate syncAggregate = data.emptySyncAggregate(); - final int syncAggregateBlockRewards = + final long syncAggregateBlockRewards = provider.calculateProposerSyncAggregateBlockRewards(reward, syncAggregate); - assertThat(syncAggregateBlockRewards).isEqualTo(0); + assertThat(syncAggregateBlockRewards).isEqualTo(0L); } @Test @@ -590,13 +632,12 @@ public void calculateProposerSlashingsRewards_shouldCalculateRewards() { final Spec spec = TestSpecFactory.createMinimalAltair(); final DataStructureUtil data = new DataStructureUtil(spec); final ChainDataProvider provider = setupAltairState(); - final BeaconBlockAndState blockAndState = data.randomBlockAndState(100); + final BeaconBlockAndState blockAndState = data.randomBlockAndStateWithValidatorLogic(100); - final UInt64 result = + final long result = provider.calculateProposerSlashingsRewards( blockAndState.getBlock(), blockAndState.getState()); - final UInt64 expectedReward = UInt64.valueOf(62500000); - assertThat(result).isEqualTo(expectedReward); + assertThat(result).isEqualTo(62500000L); } @Test @@ -604,13 +645,12 @@ public void calculateAttesterSlashingsRewards_shouldCalculateRewards() { final Spec spec = TestSpecFactory.createMinimalAltair(); final DataStructureUtil data = new DataStructureUtil(spec); final ChainDataProvider provider = setupAltairState(); - final BeaconBlockAndState blockAndState = data.randomBlockAndState(100); + final BeaconBlockAndState blockAndState = data.randomBlockAndStateWithValidatorLogic(100); - final UInt64 result = + final long result = provider.calculateAttesterSlashingsRewards( blockAndState.getBlock(), blockAndState.getState()); - final UInt64 expectedReward = UInt64.valueOf(62500000); - assertThat(result).isEqualTo(expectedReward); + assertThat(result).isEqualTo(62500000L); } @Test @@ -623,8 +663,6 @@ public void calculateAttestationRewards_shouldCalculateRewards() { final EpochProcessor epochProcessor = mock(EpochProcessor.class); final DataStructureUtil data = new DataStructureUtil(TestSpecFactory.createMinimalAltair()); - final tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState state = - data.randomBeaconState(100); final RewardAndPenaltyDeltas deltas = data.randomRewardAndPenaltyDeltas(100); when(spec.forMilestone(any())).thenReturn(specVersion); @@ -638,8 +676,8 @@ public void calculateAttestationRewards_shouldCalculateRewards() { final ChainDataProvider provider = new ChainDataProvider(spec, recentChainData, combinedChainDataClient); - final long result = provider.calculateAttestationRewards(state); - assertThat(result).isEqualTo(1179L); + final long result = provider.calculateAttestationRewards(); + assertThat(result).isEqualTo(0L); } @Test @@ -836,7 +874,7 @@ void getExpectedWithdrawalsFailsForHistoricRequest() { assertThatThrownBy( () -> chainDataProvider.getExpectedWithdrawalsFromState( - dataStructureUtil.randomBeaconState(UInt64.ONE), Optional.of(UInt64.ONE))) + dataStructureUtil.randomBeaconState(ONE), Optional.of(ONE))) .isInstanceOf(BadRequestException.class) .hasMessageContaining("historic"); } @@ -848,7 +886,7 @@ void getExpectedWithdrawalsFailsPreCapella() { assertThatThrownBy( () -> chainDataProvider.getExpectedWithdrawalsFromState( - data.randomBeaconState(UInt64.ONE), Optional.empty())) + data.randomBeaconState(ONE), Optional.empty())) .isInstanceOf(BadRequestException.class) .hasMessageContaining("pre-capella"); } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/BlockRewardData.java b/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/BlockRewardData.java index e3754156c7a..e0e8e4325b2 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/BlockRewardData.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/BlockRewardData.java @@ -17,22 +17,19 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; public class BlockRewardData { - private final UInt64 proposerIndex; - private final UInt64 total; - private final Long attestations; - private final UInt64 syncAggregate; - private final UInt64 proposerSlashings; - private final UInt64 attesterSlashings; + private UInt64 proposerIndex; + private final long attestations; + private final long syncAggregate; + private final long proposerSlashings; + private final long attesterSlashings; public BlockRewardData( final UInt64 proposerIndex, - final UInt64 total, - final Long attestations, - final UInt64 syncAggregate, - final UInt64 proposerSlashings, - final UInt64 attesterSlashings) { + final long attestations, + final long syncAggregate, + final long proposerSlashings, + final long attesterSlashings) { this.proposerIndex = proposerIndex; - this.total = total; this.attestations = attestations; this.syncAggregate = syncAggregate; this.proposerSlashings = proposerSlashings; @@ -43,23 +40,23 @@ public UInt64 getProposerIndex() { return proposerIndex; } - public UInt64 getTotal() { - return total; + public long getTotal() { + return attestations + syncAggregate + proposerSlashings + attesterSlashings; } - public Long getAttestations() { + public long getAttestations() { return attestations; } - public UInt64 getSyncAggregate() { + public long getSyncAggregate() { return syncAggregate; } - public UInt64 getProposerSlashings() { + public long getProposerSlashings() { return proposerSlashings; } - public UInt64 getAttesterSlashings() { + public long getAttesterSlashings() { return attesterSlashings; } @@ -72,17 +69,32 @@ public boolean equals(Object o) { return false; } BlockRewardData that = (BlockRewardData) o; - return Objects.equals(proposerIndex, that.proposerIndex) - && Objects.equals(total, that.total) - && Objects.equals(attestations, that.attestations) - && Objects.equals(syncAggregate, that.syncAggregate) - && Objects.equals(proposerSlashings, that.proposerSlashings) - && Objects.equals(attesterSlashings, that.attesterSlashings); + return attestations == that.attestations + && syncAggregate == that.syncAggregate + && proposerSlashings == that.proposerSlashings + && attesterSlashings == that.attesterSlashings + && Objects.equals(proposerIndex, that.proposerIndex); } @Override public int hashCode() { return Objects.hash( - proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings); + proposerIndex, attestations, syncAggregate, proposerSlashings, attesterSlashings); + } + + @Override + public String toString() { + return "BlockRewardData{" + + "proposerIndex=" + + proposerIndex + + ", attestations=" + + attestations + + ", syncAggregate=" + + syncAggregate + + ", proposerSlashings=" + + proposerSlashings + + ", attesterSlashings=" + + attesterSlashings + + '}'; } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 5a5447f7d8f..cd9644c2741 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -990,6 +990,11 @@ public SignedBlockAndState randomSignedBlockAndState(final BeaconState state) { return toSigned(blockAndState); } + public SignedBlockAndState randomSignedBlockAndStateWithValidatorLogic(final int validatorCount) { + final BeaconBlockAndState blockAndState = randomBlockAndStateWithValidatorLogic(validatorCount); + return toSigned(blockAndState); + } + public SignedBlockAndState toSigned(BeaconBlockAndState blockAndState) { final SignedBeaconBlock signedBlock = signedBlock(blockAndState.getBlock()); return new SignedBlockAndState(signedBlock, blockAndState.getState()); @@ -1008,11 +1013,6 @@ public BeaconBlockAndState randomBlockAndState(final BeaconState state) { return randomBlockAndState(state.getSlot(), state, randomBytes32()); } - public BeaconBlockAndState randomBlockAndState(final int validatorCount) { - final BeaconState state = randomBeaconState(validatorCount); - return randomBlockAndStateWithValidatorLogic(state.getSlot(), state, randomBytes32()); - } - private BeaconBlockAndState randomBlockAndState( final UInt64 slot, final BeaconState state, final Bytes32 parentRoot) { final BeaconBlockBody body = randomBeaconBlockBody(); @@ -1033,10 +1033,15 @@ private BeaconBlockAndState randomBlockAndState( return new BeaconBlockAndState(block, matchingState); } + public BeaconBlockAndState randomBlockAndStateWithValidatorLogic(final int validatorCount) { + final BeaconState state = randomBeaconState(validatorCount); + return randomBlockAndStateWithValidatorLogic(state.getSlot(), state, randomBytes32()); + } + private BeaconBlockAndState randomBlockAndStateWithValidatorLogic( final UInt64 slot, final BeaconState state, final Bytes32 parentRoot) { final BeaconBlockBody body = randomBeaconBlockBody(slot, state.getValidators().size()); - final UInt64 proposerIndex = randomUInt64(); + final UInt64 proposerIndex = randomUInt64(state.getValidators().size()); final BeaconBlockHeader latestHeader = new BeaconBlockHeader(slot, proposerIndex, parentRoot, Bytes32.ZERO, body.hashTreeRoot());