Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c4dd65b
add initial impl of endpoint
gfukushima May 8, 2025
bcd256c
add catch to cover address conversion failure
gfukushima May 15, 2025
b90acee
add tests for 200, 400 and 500
gfukushima May 15, 2025
c11a4bf
spotless
gfukushima May 15, 2025
eb00b79
add test for when discovery is not enabled
gfukushima May 18, 2025
1a47aab
Merge branch 'master' into add-peers-via-API
gfukushima May 18, 2025
3c38c5a
spotless
gfukushima May 19, 2025
0705d72
fix name of test after changing exception returned for invalid peer
gfukushima May 19, 2025
16ccf27
add json def to paths
gfukushima May 19, 2025
b4e1c94
add changelog
gfukushima May 19, 2025
cf8505e
PR review
gfukushima May 19, 2025
86533d4
spotless caught me again
gfukushima May 19, 2025
6018d3f
change impl and tests to handle a single peer per api call
gfukushima May 20, 2025
112ff35
spotless
gfukushima May 20, 2025
b5867bc
Merge branch 'master' into add-peers-via-API
gfukushima May 20, 2025
71d7818
make request body type string rather than list of strings
gfukushima May 21, 2025
1b0415f
Merge remote-tracking branch 'origin/add-peers-via-API' into add-peer…
gfukushima May 21, 2025
d8b9b73
spotless
gfukushima May 21, 2025
deb7afb
specify what type of address needs to be passed
gfukushima May 21, 2025
1850b11
remove typo
gfukushima May 21, 2025
eb03818
Merge branch 'master' into add-peers-via-API
gfukushima May 21, 2025
618d097
adding description of the request body to the field itself
gfukushima May 21, 2025
3cb11ba
adding description to the field
gfukushima May 21, 2025
7423c7c
Merge branch 'master' into add-peers-via-API
gfukushima May 21, 2025
7fa6a73
Merge branch 'master' into add-peers-via-API
gfukushima May 21, 2025
e9f5674
adding the supported type of address to method description as well as…
gfukushima May 21, 2025
8199f8d
Merge remote-tracking branch 'origin/add-peers-via-API' into add-peer…
gfukushima May 21, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
- Added jdk 24 docker image build.
- Improved performance when scheduling attestations in the beginning of the epoch for a large number of validators.
- Improved configuration loading to use builtin configurations to default any fields we need that were missing from a passed in configuration.
- Add `/teku/v1/admin/add_peer` endpoint to allow adding static peers via the REST API.

### Bug Fixes
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"post" : {
"tags" : [ "Teku" ],
"operationId" : "AddPeer",
"summary" : "Add a static peer to the node",
"description" : "Add a static peer to the node passing a multiaddress.",
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"type" : "string",
"description" : "Multiaddress of the peer to add"
}
}
}
},
"responses" : {
"200" : {
"description" : "Peer added successfully",
Comment thread
rolfyone marked this conversation as resolved.
"content" : { }
},
"400" : {
"description" : "Invalid peer address",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"500" : {
"description" : "Internal server error",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tech.pegasys.teku.beaconrestapi.addon.CapellaRestApiBuilderAddon;
import tech.pegasys.teku.beaconrestapi.addon.DenebRestApiBuilderAddon;
import tech.pegasys.teku.beaconrestapi.addon.LightClientRestApiBuilderAddon;
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.AddPeer;
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.Liveness;
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.PutLogLevel;
import tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin.Readiness;
Expand Down Expand Up @@ -322,7 +323,8 @@ private static RestApi create(
.endpoint(new GetEth1VotingSummary(dataProvider, eth1DataProvider))
.endpoint(new GetGlobalValidatorInclusion(dataProvider))
.endpoint(new GetFinalizedStateSlotBefore(dataProvider))
.endpoint(new GetValidatorInclusion(dataProvider));
.endpoint(new GetValidatorInclusion(dataProvider))
.endpoint(new AddPeer(dataProvider));

builder = applyAddons(builder, config, spec, dataProvider, schemaCache);
return builder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright Consensys Software Inc., 2025
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin;

import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_TEKU;
import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.STRING_TYPE;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Optional;
import tech.pegasys.teku.api.DataProvider;
import tech.pegasys.teku.api.NetworkDataProvider;
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork;

public class AddPeer extends RestApiEndpoint {

public static final String ROUTE = "/teku/v1/admin/add_peer";
final NetworkDataProvider networkDataProvider;

public AddPeer(final DataProvider dataProvider) {
this(dataProvider.getNetworkDataProvider());
}

AddPeer(final NetworkDataProvider networkDataProvider) {
super(
EndpointMetadata.post(ROUTE)
.operationId("AddPeer")
.summary("Add a static peer to the node")
.description("Add a static peer to the node passing a multiaddress.")
.tags(TAG_TEKU)
.requestBodyType(STRING_TYPE.withDescription("Multiaddress of the peer to add"))
.response(SC_OK, "Peer added successfully")
.withBadRequestResponse(Optional.of("Invalid peer address"))
.withInternalErrorResponse()
.build());
this.networkDataProvider = networkDataProvider;
}

@Override
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
try {

final String peerAddress = request.getRequestBody();
if (peerAddress.isEmpty()) {
request.respondWithCode(SC_OK);
return;
}

final DiscoveryNetwork<?> discoveryNetwork =
networkDataProvider
.getDiscoveryNetwork()
.orElseThrow(() -> new IllegalStateException("Discovery network not available"));

discoveryNetwork.addStaticPeer(peerAddress);
request.respondWithCode(SC_OK);
} catch (final IllegalArgumentException e) {
request.respondError(SC_BAD_REQUEST, e.getMessage());
} catch (final IllegalStateException e) {
request.respondError(SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright Consensys Software Inc., 2025
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.beaconrestapi.handlers.tekuv1.admin;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR;
import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK;
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataEmptyResponse;
import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest;
import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork;

class AddPeerTest extends AbstractMigratedBeaconHandlerTest {

private final DiscoveryNetwork<?> discoveryNetwork = mock(DiscoveryNetwork.class);

@BeforeEach
public void setup() {
setHandler(new AddPeer(network));
}

@Test
void metadata_shouldHandle200() {
verifyMetadataEmptyResponse(handler, SC_OK);
}

@Test
void metadata_shouldHandle400() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_BAD_REQUEST);
}

@Test
void metadata_shouldHandle500() throws JsonProcessingException {
verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR);
}

@Test
public void shouldReturnOkWhenAValidListIsProvided() throws Exception {
final String peerAddress = "/ip4/someIP/udp/9001/p2p/somePeerId";
request.setRequestBody(peerAddress);
when(network.getDiscoveryNetwork()).thenReturn(Optional.of(discoveryNetwork));
handler.handleRequest(request);
assertThat(request.getResponseCode()).isEqualTo(SC_OK);
}

@Test
public void shouldReturnOkWhenRequestBodyIsEmpty() throws Exception {
final String peerAddress = "";
request.setRequestBody(peerAddress);
when(network.getDiscoveryNetwork()).thenReturn(Optional.of(discoveryNetwork));
handler.handleRequest(request);
assertThat(request.getResponseCode()).isEqualTo(SC_OK);
}

@Test
public void shouldReturnBadRequestIfInvalidPeerAddress() throws Exception {
final String peerAddress = "invalid-peer-address";
request.setRequestBody(peerAddress);
when(network.getDiscoveryNetwork()).thenReturn(Optional.of(discoveryNetwork));
doThrow(new IllegalArgumentException("Invalid peer address"))
.when(discoveryNetwork)
.addStaticPeer(peerAddress);
handler.handleRequest(request);
assertThat(request.getResponseCode()).isEqualTo(SC_BAD_REQUEST);
}

@Test
public void shouldReturnInternalErrorWhenDiscoveryNetworkNotAvailable() throws Exception {
final String peerAddress = "/ip4/someIP/udp/9001/p2p/somePeerId";
request.setRequestBody(peerAddress);
when(network.getDiscoveryNetwork()).thenReturn(Optional.empty());
handler.handleRequest(request);
assertThat(request.getResponseCode()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,8 @@ private Peer toPeer(final Eth2Peer eth2Peer) {

return new Peer(peerId, null, address, state, direction);
}

public Optional<DiscoveryNetwork<?>> getDiscoveryNetwork() {
return network.getDiscoveryNetwork();
}
}