Skip to content
Merged
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ View logs output and results:
```shell script
./hiveview --serve --logdir ./workspace/logs
```
## [JSON-RPC API (20)](https://samba-portal-node.postman.co/workspace/Samba-Portal-Node-Workspace~8bf54719-5e6d-4476-8b33-6434dc57d833/request/33150235-eb63c4bf-82ff-477e-a17d-616657e9cdbc?action=share&creator=33150235&ctx=documentation&active-environment=33150235-5c222146-bd60-431b-bb15-f3f9dc8fc9cc)
## [JSON-RPC API (23)](https://samba-portal-node.postman.co/workspace/Samba-Portal-Node-Workspace~8bf54719-5e6d-4476-8b33-6434dc57d833/request/33150235-eb63c4bf-82ff-477e-a17d-616657e9cdbc?action=share&creator=33150235&ctx=documentation&active-environment=33150235-5c222146-bd60-431b-bb15-f3f9dc8fc9cc)

#### History
- portal_historyAddEnr
Expand All @@ -140,6 +140,7 @@ View logs output and results:
- portal_historyPing
- portal_historyStore
- portal_historyPutContent
- portal_historyRoutingTableInfo

#### Discv5
- discv5_getEnr,
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/samba/api/HistoryAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Optional<Bytes> offer(

Optional<RecursiveFindNodesResult> recursiveFindNodes(final String nodeId);

Optional<List<List<String>>> getRoutingTable();

// For Besu

Optional<BlockHeader> getBlockHeaderByBlockHash(Hash blockHash);
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/samba/api/HistoryAPIClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public Optional<RecursiveFindNodesResult> recursiveFindNodes(String nodeId) {
return RecursiveFindNodes.execute(this.historyNetworkInternalAPI, nodeId);
}

@Override
public Optional<List<List<String>>> getRoutingTable() {
return GetRoutingTable.execute(this.historyNetworkInternalAPI);
}

@Override
public Optional<BlockHeader> getBlockHeaderByBlockHash(Hash blockHash) {
return GetBlockHeaderByBlockHash.execute(this.historyNetworkInternalAPI, blockHash);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package samba.api.jsonrpc;

import samba.api.Discv5API;
import samba.api.HistoryAPI;
import samba.api.jsonrpc.results.RoutingTableInfoResult;
import samba.jsonrpc.config.RpcMethod;
import samba.jsonrpc.reponse.JsonRpcMethod;
import samba.jsonrpc.reponse.JsonRpcRequestContext;
import samba.jsonrpc.reponse.JsonRpcResponse;
import samba.jsonrpc.reponse.RpcErrorType;

public class PortalHistoryRoutingTableInfo implements JsonRpcMethod {

private final HistoryAPI historyAPI;
private final Discv5API discv5API;

public PortalHistoryRoutingTableInfo(final HistoryAPI historyAPI, final Discv5API discv5API) {
this.historyAPI = historyAPI;
this.discv5API = discv5API;
}

@Override
public String getName() {
return RpcMethod.PORTAL_HISTORY_ROUTING_TABLE_INFO.getMethodName();
}

@Override
public JsonRpcResponse response(JsonRpcRequestContext requestContext) {
return this.discv5API
.getNodeInfo()
.flatMap(
info ->
this.historyAPI
.getRoutingTable()
.map(
table -> {
RoutingTableInfoResult result =
new RoutingTableInfoResult(info.getNodeId(), table);
return createSuccessResponse(requestContext, result);
}))
.orElseGet(
() ->
createJsonRpcInvalidRequestResponse(requestContext, RpcErrorType.INVALID_REQUEST));
}
}
5 changes: 5 additions & 0 deletions core/src/main/java/samba/domain/dht/NodeTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.Clock;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
Expand Down Expand Up @@ -95,4 +96,8 @@ private Optional<KBucket> getBucket(final int distance) {
public boolean isNodeIgnored(NodeRecord nodeRecord) {
return this.livenessManager.isABadPeer(nodeRecord);
}

public List<List<NodeRecord>> getNodeRecordBuckets() {
return this.buckets.values().stream().map(KBucket::getAllNodes).toList();
}
}
5 changes: 5 additions & 0 deletions core/src/main/java/samba/network/history/HistoryNetwork.java
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,11 @@ public Optional<TraceGetContentResult> traceGetContent(
}
}

@Override
public List<List<NodeRecord>> getRoutingTable() {
return this.routingTable.getNodeRecordBuckets();
}

@Override
public Optional<RecursiveFindNodesResult> recursiveFindNodes(
final String nodeId, Set<NodeRecord> excludedNodes, final int timeout) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ Optional<RecursiveFindNodesResult> recursiveFindNodes(

Optional<TraceGetContentResult> traceGetContent(
ContentKey contentKey, int timeout, long startTime);

List<List<NodeRecord>> getRoutingTable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package samba.network.history.api.methods;

import samba.network.history.api.HistoryNetworkInternalAPI;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.tuweni.bytes.Bytes;
import org.ethereum.beacon.discovery.schema.NodeRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GetRoutingTable {

private static final Logger LOG = LoggerFactory.getLogger(GetRoutingTable.class);
public static final int MAX_ROUTING_TABLE_SIZE = 16;

private final HistoryNetworkInternalAPI historyNetworkInternalAPI;

public GetRoutingTable(final HistoryNetworkInternalAPI historyNetworkInternalAPI) {
this.historyNetworkInternalAPI = historyNetworkInternalAPI;
}

private Optional<List<List<String>>> execute() {
List<List<NodeRecord>> routingTable = historyNetworkInternalAPI.getRoutingTable();
return Optional.of(
routingTable.stream()
.map(
innerList -> {
List<String> reversed =
innerList.stream()
.map(NodeRecord::getNodeId)
.map(Bytes::toHexString)
.limit(MAX_ROUTING_TABLE_SIZE)
.collect(Collectors.toList());
Collections.reverse(reversed); // ordered from least-recently to most-recently
return reversed;
})
.collect(Collectors.toList()));
}

public static Optional<List<List<String>>> execute(
final HistoryNetworkInternalAPI historyNetworkInternalAPI) {
LOG.debug("Executing GetRoutingTable");
return new GetRoutingTable(historyNetworkInternalAPI).execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -103,6 +104,11 @@ public boolean isNodeIgnored(NodeRecord nodeRecord) {
return this.nodeTable.isNodeIgnored(nodeRecord);
}

@Override
public List<List<NodeRecord>> getNodeRecordBuckets() {
return this.nodeTable.getNodeRecordBuckets();
}

@Override
public Optional<NodeRecord> findClosestNodeToKey(Bytes key) {
return radiusMap.entrySet().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package samba.network.history.routingtable;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
Expand Down Expand Up @@ -33,4 +34,7 @@ public interface RoutingTable {
boolean isNodeConnected(Bytes nodeId);

boolean isNodeIgnored(NodeRecord nodeRecord);

/** Recently nodes are at the start of the list with older at the end. */
List<List<NodeRecord>> getNodeRecordBuckets();
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ private void initJsonRPCService() {
RpcMethod.PORTAL_HISTORY_RECURSIVE_FIND_NODES.getMethodName(),
new PortalHistoryRecursiveFindNodes(this.historyAPI));
methods.put(RpcMethod.PORTAL_BEACON_STORE.getMethodName(), new PortalBeaconStore());
methods.put(
RpcMethod.PORTAL_HISTORY_ROUTING_TABLE_INFO.getMethodName(),
new PortalHistoryRoutingTableInfo(this.historyAPI, this.discv5API));

jsonRpcService =
Optional.of(
Expand Down
131 changes: 131 additions & 0 deletions core/src/test/java/samba/SimpleIdentitySchemaInterpreter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package samba;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.MutableBytes;
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
import org.apache.tuweni.units.bigints.UInt64;
import org.ethereum.beacon.discovery.schema.EnrField;
import org.ethereum.beacon.discovery.schema.IdentitySchema;
import org.ethereum.beacon.discovery.schema.IdentitySchemaInterpreter;
import org.ethereum.beacon.discovery.schema.NodeRecord;
import org.ethereum.beacon.discovery.schema.NodeRecordFactory;

public class SimpleIdentitySchemaInterpreter implements IdentitySchemaInterpreter {

public static NodeRecord createNodeRecord(final int nodeId) {
return createNodeRecord(Bytes.ofUnsignedInt(nodeId));
}

public static NodeRecord createNodeRecord(
final Bytes nodeId, final InetSocketAddress udpAddress) {
return createNodeRecord(
nodeId,
new EnrField(EnrField.IP_V4, Bytes.wrap(udpAddress.getAddress().getAddress())),
new EnrField(EnrField.UDP, udpAddress.getPort()));
}

public static NodeRecord createNodeRecord(final Bytes nodeId, final EnrField... extraFields) {
final List<EnrField> fields = new ArrayList<>(List.of(extraFields));
fields.add(new EnrField(EnrField.ID, IdentitySchema.V4));
fields.add(new EnrField(EnrField.PKEY_SECP256K1, nodeId));
return new NodeRecordFactory(new SimpleIdentitySchemaInterpreter())
.createFromValues(UInt64.ONE, fields);
}

@Override
public IdentitySchema getScheme() {
return IdentitySchema.V4;
}

@Override
public void sign(final NodeRecord nodeRecord, final SecretKey secretKey) {
nodeRecord.setSignature(MutableBytes.create(96));
}

@Override
public Bytes getNodeId(final NodeRecord nodeRecord) {
Bytes prototype = (Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1);
// Aligning it for correct 32 bytes
if (prototype.size() <= 32) {
return Bytes32.leftPad(prototype);
} else {
return prototype.slice(0, 32);
}
}

@Override
public Optional<InetSocketAddress> getUdpAddress(final NodeRecord nodeRecord) {
try {
final Bytes ipBytes = (Bytes) nodeRecord.get(EnrField.IP_V4);
if (ipBytes == null) {
return Optional.empty();
}
final InetAddress ipAddress = InetAddress.getByAddress(ipBytes.toArrayUnsafe());
final int port = (int) nodeRecord.get(EnrField.UDP);
return Optional.of(new InetSocketAddress(ipAddress, port));
} catch (UnknownHostException e) {
return Optional.empty();
}
}

@Override
public Optional<InetSocketAddress> getUdp6Address(NodeRecord nodeRecord) {
return Optional.empty();
}

@Override
public Optional<InetSocketAddress> getTcpAddress(final NodeRecord nodeRecord) {
return Optional.empty();
}

@Override
public Optional<InetSocketAddress> getTcp6Address(NodeRecord nodeRecord) {
return Optional.empty();
}

@Override
public NodeRecord createWithNewAddress(
NodeRecord nodeRecord,
InetSocketAddress inetSocketAddress,
Optional<Integer> optional,
SecretKey secretKey) {
return null;
}

@Override
public NodeRecord createWithUpdatedCustomField(
final NodeRecord nodeRecord,
final String fieldName,
final Bytes value,
final SecretKey secretKey) {
final List<EnrField> fields = new ArrayList<>();
nodeRecord.forEachField(
(key, existingValue) -> {
if (!key.equals(fieldName)) {
fields.add(new EnrField(key, existingValue));
}
});
fields.add(new EnrField(fieldName, value));
final NodeRecord newRecord = NodeRecord.fromValues(this, nodeRecord.getSeq().add(1), fields);
sign(newRecord, secretKey);
return newRecord;
}

@Override
public Bytes calculateNodeId(final Bytes publicKey) {
final NodeRecord nodeRecord =
createNodeRecord(publicKey, new InetSocketAddress("127.0.0.1", 2));
return nodeRecord.getNodeId();
}
}
36 changes: 36 additions & 0 deletions core/src/test/java/samba/TestHelper.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package samba;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Random;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt64;
import org.ethereum.beacon.discovery.schema.EnrField;
import org.ethereum.beacon.discovery.schema.IdentitySchema;
import org.ethereum.beacon.discovery.schema.NodeRecord;
import org.ethereum.beacon.discovery.schema.NodeRecordBuilder;
import org.ethereum.beacon.discovery.schema.NodeRecordFactory;
import org.ethereum.beacon.discovery.util.Functions;

public class TestHelper {
Expand All @@ -13,4 +22,31 @@ public static NodeRecord createNodeRecord() {
.secretKey(Functions.randomKeyPair(new Random(new Random().nextInt())).secretKey())
.build();
}

public static NodeRecord createNodeAtDistance(final Bytes sourceNode, final int distance) {
final BitSet bits = BitSet.valueOf(sourceNode.reverse().toArray());
bits.flip(distance - 1);
final byte[] targetNodeId = new byte[sourceNode.size()];
final byte[] src = bits.toByteArray();
System.arraycopy(src, 0, targetNodeId, 0, src.length);
final Bytes nodeId = Bytes.wrap(targetNodeId).reverse();
return SimpleIdentitySchemaInterpreter.createNodeRecord(
nodeId, new InetSocketAddress("127.0.0.1", 2));
}

public static NodeRecord createNodeRecord(
final Bytes nodeId, final InetSocketAddress udpAddress) {
return createNodeRecord(
nodeId,
new EnrField(EnrField.IP_V4, Bytes.wrap(udpAddress.getAddress().getAddress())),
new EnrField(EnrField.UDP, udpAddress.getPort()));
}

public static NodeRecord createNodeRecord(final Bytes nodeId, final EnrField... extraFields) {
final List<EnrField> fields = new ArrayList<>(List.of(extraFields));
fields.add(new EnrField(EnrField.ID, IdentitySchema.V4));
fields.add(new EnrField(EnrField.PKEY_SECP256K1, nodeId));
return new NodeRecordFactory(new SimpleIdentitySchemaInterpreter())
.createFromValues(UInt64.ONE, fields);
}
}
Loading
Loading