Skip to content

Commit b69c8d7

Browse files
committed
Add NodeStatus service package and docs
Introduce a new EntglDb.Services.NodeStatus project implementing a node-status diagnostic service. Added: project file (csproj) with Proto/Grpc dependencies, nodestatus.proto (wire types 1000/1001), INodeStatusService API, NodeStatusClient (uses IPeerMessenger), NodeStatusHandler (INetworkMessageHandler responder), NodeStatusInfo model, and DI extension AddEntglDbNodeStatus. Also updated README examples and docs to clarify custom message type ranges (32–999) and that official packages use 1000+; updated sync README examples to use a sample custom type (100). This enables querying remote peer runtime status over the shared EntglDb P2P connection.
1 parent c478e40 commit b69c8d7

9 files changed

Lines changed: 285 additions & 5 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,8 @@ Reserve a message type constant (≥ 32):
537537
```csharp
538538
public static class MyMessageType
539539
{
540-
public const int PingRequest = 32;
541-
public const int PingResponse = 33;
540+
public const int PingRequest = 100; // any value in the 32–999 custom range
541+
public const int PingResponse = 101;
542542
}
543543
```
544544

@@ -637,7 +637,8 @@ builder.Services.AddSingleton<MyClientService>();
637637
| 0–2 | Protocol control (handshake, keepalive) |
638638
| 3–15 | Built-in sync messages (`SyncMessageType`) |
639639
| 16–31 | Reserved for future EntglDb use |
640-
| **32+** | **Your custom services** |
640+
| **32–999** | **Your custom services** |
641+
| 1000+ | Official EntglDb service packages (e.g. `EntglDb.Services.NodeStatus` uses 1000–1001) |
641642

642643
---
643644

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.1;net10.0</TargetFrameworks>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<Version>1.0.0</Version>
9+
<Authors>MrDevRobot</Authors>
10+
<Description>Node status diagnostic service for the EntglDb P2P mesh network. Allows any node to query the runtime status of remote peers over the shared TCP connection.</Description>
11+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
12+
<PackageTags>p2p;mesh;entgldb;distributed;diagnostics;nodestatus</PackageTags>
13+
<PackageProjectUrl>https://github.com/EntglDb/EntglDb.Net</PackageProjectUrl>
14+
<RepositoryUrl>https://github.com/EntglDb/EntglDb.Net</RepositoryUrl>
15+
<RepositoryType>git</RepositoryType>
16+
<PackageReadmeFile>README.md</PackageReadmeFile>
17+
<PackageIcon>icon.png</PackageIcon>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<None Include="README.md" Pack="true" PackagePath="\" />
22+
<None Include="..\..\assets\icon.png" Pack="true" PackagePath="\" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\EntglDb.Network\EntglDb.Network.csproj" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<PackageReference Include="Google.Protobuf" Version="3.34.1" />
31+
<PackageReference Include="Grpc.Tools" Version="2.78.0">
32+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
33+
<PrivateAssets>all</PrivateAssets>
34+
</PackageReference>
35+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
36+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
37+
</ItemGroup>
38+
39+
<ItemGroup>
40+
<Protobuf Include="nodestatus.proto" GrpcServices="None" />
41+
</ItemGroup>
42+
43+
</Project>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
4+
namespace EntglDb.Services.NodeStatus;
5+
6+
/// <summary>
7+
/// Client-side API for querying the runtime status of a remote peer
8+
/// over the EntglDb P2P mesh network.
9+
/// </summary>
10+
/// <remarks>
11+
/// Inject this service and call <see cref="QueryAsync"/> with the peer's
12+
/// <c>host:port</c> address. The underlying TCP connection is shared with
13+
/// the sync engine — no extra socket is opened.
14+
/// <code>
15+
/// var status = await nodeStatusService.QueryAsync("192.168.1.10:7000", ct);
16+
/// Console.WriteLine($"{status.NodeId} up for {status.Uptime.TotalMinutes:F0} min");
17+
/// </code>
18+
/// </remarks>
19+
public interface INodeStatusService
20+
{
21+
/// <summary>
22+
/// Queries a remote peer for its current runtime status.
23+
/// </summary>
24+
/// <param name="peerAddress">Remote peer address in <c>host:port</c> format.</param>
25+
/// <param name="ct">Cancellation token.</param>
26+
/// <returns>A <see cref="NodeStatusInfo"/> snapshot from the remote peer.</returns>
27+
Task<NodeStatusInfo> QueryAsync(string peerAddress, CancellationToken ct = default);
28+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using EntglDb.Network;
5+
using EntglDb.Services.NodeStatus.Proto;
6+
7+
namespace EntglDb.Services.NodeStatus;
8+
9+
/// <summary>
10+
/// Default implementation of <see cref="INodeStatusService"/>.
11+
/// Uses <see cref="IPeerMessenger"/> to send a <see cref="NodeStatusRequest"/> to the remote peer
12+
/// and parse the <see cref="NodeStatusResponse"/> back as a <see cref="NodeStatusInfo"/>.
13+
/// </summary>
14+
internal sealed class NodeStatusClient : INodeStatusService
15+
{
16+
private readonly IPeerMessenger _messenger;
17+
18+
public NodeStatusClient(IPeerMessenger messenger)
19+
{
20+
_messenger = messenger;
21+
}
22+
23+
/// <inheritdoc/>
24+
public async Task<NodeStatusInfo> QueryAsync(string peerAddress, CancellationToken ct = default)
25+
{
26+
var (responseType, payload) = await _messenger.SendAndReceiveAsync(
27+
peerAddress,
28+
(int)NodeStatusMessageType.NodeStatusReq,
29+
new NodeStatusRequest(),
30+
ct);
31+
32+
if (responseType != (int)NodeStatusMessageType.NodeStatusRes)
33+
throw new InvalidOperationException(
34+
$"Unexpected response type {responseType} from peer '{peerAddress}'.");
35+
36+
var proto = NodeStatusResponse.Parser.ParseFrom(payload);
37+
38+
return new NodeStatusInfo
39+
{
40+
NodeId = proto.NodeId,
41+
Uptime = TimeSpan.FromSeconds(proto.UptimeSeconds),
42+
KnownPeerAddresses = proto.KnownPeerAddresses,
43+
};
44+
}
45+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using EntglDb.Network;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.DependencyInjection.Extensions;
4+
5+
namespace EntglDb.Services.NodeStatus;
6+
7+
/// <summary>
8+
/// Extension methods for registering EntglDb node-status diagnostic services.
9+
/// </summary>
10+
public static class NodeStatusExtensions
11+
{
12+
/// <summary>
13+
/// Adds the EntglDb node-status service to the DI container.
14+
/// </summary>
15+
/// <remarks>
16+
/// <para>
17+
/// Registers:
18+
/// <list type="bullet">
19+
/// <item><see cref="NodeStatusHandler"/> as <see cref="INetworkMessageHandler"/> (server-side responder, wire type 1000)</item>
20+
/// <item><see cref="INodeStatusService"/> / <see cref="NodeStatusClient"/> (client-side query API)</item>
21+
/// </list>
22+
/// </para>
23+
/// <para>
24+
/// Call <c>AddEntglDbNetwork&lt;T&gt;()</c> before this method so that
25+
/// <see cref="IPeerMessenger"/> is already registered.
26+
/// </para>
27+
/// </remarks>
28+
public static IServiceCollection AddEntglDbNodeStatus(this IServiceCollection services)
29+
{
30+
// Server side: handle incoming NodeStatusRequest messages (wire type 1000)
31+
services.AddSingleton<INetworkMessageHandler, NodeStatusHandler>();
32+
33+
// Client side: let consumers query remote peers
34+
services.TryAddSingleton<INodeStatusService, NodeStatusClient>();
35+
36+
return services;
37+
}
38+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using EntglDb.Core.Network;
5+
using EntglDb.Network;
6+
using EntglDb.Services.NodeStatus.Proto;
7+
using Google.Protobuf;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace EntglDb.Services.NodeStatus;
11+
12+
/// <summary>
13+
/// Server-side handler that responds to <see cref="NodeStatusMessageType.NodeStatusReq"/> (wire type 1000)
14+
/// with a snapshot of the local node's runtime status.
15+
/// </summary>
16+
/// <remarks>
17+
/// Register this handler in DI <em>after</em> <c>AddEntglDbNetwork</c>:
18+
/// <code>
19+
/// services.AddEntglDbNetwork&lt;MyConfig&gt;();
20+
/// services.AddEntglDbNodeStatus(); // registers NodeStatusHandler + INodeStatusService
21+
/// </code>
22+
/// </remarks>
23+
public sealed class NodeStatusHandler : INetworkMessageHandler
24+
{
25+
private readonly IPeerNodeConfigurationProvider _configProvider;
26+
private readonly IDiscoveryService _discovery;
27+
private readonly ILogger<NodeStatusHandler> _logger;
28+
private readonly DateTimeOffset _startTime = DateTimeOffset.UtcNow;
29+
30+
// Assembly version is resolved once at construction time.
31+
private static readonly string ServiceVersion =
32+
typeof(NodeStatusHandler).Assembly.GetName().Version?.ToString() ?? "unknown";
33+
34+
public int MessageType => (int)NodeStatusMessageType.NodeStatusReq;
35+
36+
public NodeStatusHandler(
37+
IPeerNodeConfigurationProvider configProvider,
38+
IDiscoveryService discovery,
39+
ILogger<NodeStatusHandler> logger)
40+
{
41+
_configProvider = configProvider;
42+
_discovery = discovery;
43+
_logger = logger;
44+
}
45+
46+
/// <inheritdoc/>
47+
public async Task<(IMessage? Response, int ResponseType)> HandleAsync(IMessageHandlerContext context)
48+
{
49+
var config = await _configProvider.GetConfiguration();
50+
51+
var activePeers = _discovery.GetActivePeers().ToList();
52+
53+
var response = new NodeStatusResponse
54+
{
55+
NodeId = config.NodeId,
56+
UptimeSeconds = (long)(DateTimeOffset.UtcNow - _startTime).TotalSeconds,
57+
KnownPeerCount = activePeers.Count,
58+
ServiceVersion = ServiceVersion,
59+
};
60+
61+
foreach (var peer in activePeers)
62+
response.KnownPeerAddresses.Add(peer.Address);
63+
64+
_logger.LogDebug("NodeStatus query from {Remote}: responded with {PeerCount} known peers",
65+
context.RemoteEndPoint, activePeers.Count);
66+
67+
return (response, (int)NodeStatusMessageType.NodeStatusRes);
68+
}
69+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace EntglDb.Services.NodeStatus;
5+
6+
/// <summary>
7+
/// Runtime status snapshot reported by a remote peer.
8+
/// </summary>
9+
public sealed class NodeStatusInfo
10+
{
11+
/// <summary>The unique node identifier of the responding peer.</summary>
12+
public string NodeId { get; init; } = string.Empty;
13+
14+
/// <summary>How long the peer process has been running.</summary>
15+
public TimeSpan Uptime { get; init; }
16+
17+
/// <summary>Addresses (<c>host:port</c>) of all peers currently visible to the responding node.</summary>
18+
public IReadOnlyList<string> KnownPeerAddresses { get; init; } = Array.Empty<string>();
19+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
syntax = "proto3";
2+
3+
package EntglDb.Services.NodeStatus.Proto;
4+
5+
option csharp_namespace = "EntglDb.Services.NodeStatus.Proto";
6+
7+
// Wire values 1000–1001 are reserved for EntglDb.Services.NodeStatus.
8+
// User-defined services should pick blocks that do not overlap with other
9+
// registered packages. The range 32–999 is available for custom use;
10+
// ranges above 1000 are used by official EntglDb service packages.
11+
enum NodeStatusMessageType {
12+
NodeStatusUnknown = 0; // Required by proto3; never sent on the wire.
13+
NodeStatusReq = 1000;
14+
NodeStatusRes = 1001;
15+
}
16+
17+
// Sent by a node that wants to know the remote peer's runtime status.
18+
message NodeStatusRequest {
19+
}
20+
21+
// Returned by the remote peer with its current runtime status.
22+
message NodeStatusResponse {
23+
// The unique node identifier of the responding peer.
24+
string node_id = 1;
25+
26+
// How long the node process has been running, in whole seconds.
27+
int64 uptime_seconds = 2;
28+
29+
// Number of peers currently visible to this node via discovery.
30+
int32 known_peer_count = 3;
31+
32+
// Addresses (host:port) of all currently active peers.
33+
repeated string known_peer_addresses = 4;
34+
35+
// Semantic version string of the running EntglDb.Services.NodeStatus package.
36+
string service_version = 5;
37+
}

src/EntglDb.Sync/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Message types ≥ 32 are reserved for user services.
3939
// Server side — handle incoming requests
4040
public class PingHandler : INetworkMessageHandler
4141
{
42-
public int MessageType => 32;
42+
public int MessageType => 100; // pick any value in the 32–999 custom range
4343
4444
public Task<(IMessage? Response, int ResponseType)> HandleAsync(IMessageHandlerContext context)
4545
{
@@ -54,7 +54,7 @@ services.AddSingleton<INetworkMessageHandler, PingHandler>();
5454

5555
// Client side — send a request
5656
var (_, payload) = await messenger.SendAndReceiveAsync(
57-
"192.168.1.10:7000", 32, new PingRequest { Message = "hello" }, ct);
57+
"192.168.1.10:7000", 100, new PingRequest { Message = "hello" }, ct);
5858
var response = PingResponse.Parser.ParseFrom(payload);
5959
```
6060

0 commit comments

Comments
 (0)