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
2 changes: 1 addition & 1 deletion boot.tests/NodeSetupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void SetupModeAllowsOnlySetupAndHealthPaths()
{
Assert.IsTrue(NodeSetupPolicy.IsAllowedSetupPath("/setup"));
Assert.IsTrue(NodeSetupPolicy.IsAllowedSetupPath("/setup.css"));
Assert.IsTrue(NodeSetupPolicy.IsAllowedSetupPath("/setup.js"));
Assert.IsFalse(NodeSetupPolicy.IsAllowedSetupPath("/setup.js"));
Assert.IsTrue(NodeSetupPolicy.IsAllowedSetupPath("/health/live"));
Assert.IsTrue(NodeSetupPolicy.IsAllowedSetupPath("/health/ready"));
Assert.IsFalse(NodeSetupPolicy.IsAllowedSetupPath("/api/mining/share"));
Expand Down
21 changes: 21 additions & 0 deletions boot.tests/SecurityHardeningTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ public void PrivateDashboardEnvironmentOverrideSupportsPackageUpgrades()
}
}

[TestMethod]
public void NativeSv2AuthorityEnvironmentOverrideSupportsPackageUpgrades()
{
const string variable = "GRIDPOOL_NATIVE_SV2_AUTHORITY_PUBLIC_KEY";
const string publicKey = "9exampleAuthorityPublicKey";
string? previous = Environment.GetEnvironmentVariable(variable);
try
{
var config = ValidConfig();
Environment.SetEnvironmentVariable(variable, $" {publicKey} ");

Program.ApplyPoolConfigEnvironmentOverrides(config);

Assert.AreEqual(publicKey, config.NativeSv2AuthorityPublicKey);
}
finally
{
Environment.SetEnvironmentVariable(variable, previous);
}
}

[TestMethod]
public void StoredMinerLabelIsBoundedAndMarkupFree()
{
Expand Down
1 change: 1 addition & 0 deletions boot_portal/Models/DashboardModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public sealed class DashboardNativeSv2Dto
public bool Enabled { get; set; }
public string PublicHost { get; set; } = string.Empty;
public int PublicPort { get; set; } = 34265;
public string AuthorityPublicKey { get; set; } = string.Empty;
public string Scheme { get; set; } = "stratum2+noise";
public string UsernameGuidance { get; set; } =
"Use a Bitcoin payout address for per-miner slot-0 attribution, or a worker label to use the node payout address.";
Expand Down
3 changes: 3 additions & 0 deletions boot_portal/Models/PoolConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ public class PoolConfig
[JsonPropertyName("native_sv2_public_port")]
public int NativeSv2PublicPort { get; set; } = 34265;

[JsonPropertyName("native_sv2_authority_public_key")]
public string NativeSv2AuthorityPublicKey { get; set; } = string.Empty;

[JsonPropertyName("admin_rate_limit_per_minute")]
public int AdminRateLimitPerMinute { get; set; } = 12;

Expand Down
9 changes: 4 additions & 5 deletions boot_portal/Pages/Setup.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,22 @@
<dl>
<div><dt>Host</dt><dd>@Model.NativeSv2Host</dd></div>
<div><dt>Port</dt><dd>@Model.NativeSv2Port</dd></div>
<div><dt>Authority key</dt><dd><code>@Model.NativeSv2AuthorityPublicKey</code></dd></div>
<div><dt>Username</dt><dd>Your payout address or worker label</dd></div>
</dl>
<p>AxeOS users must paste the authority key into <strong>SV2 Authority Pubkey</strong> under the pool's advanced options.</p>
<p>If <code>@Model.NativeSv2Host</code> does not resolve from your miner, use this Umbrel device's LAN IP with port @Model.NativeSv2Port.</p>
</section>
}
@if (Model.AutomaticRestart)
{
<p class="safety-note" id="restart-status">Waiting for the node to become ready. This page will open the dashboard automatically.</p>
<p class="safety-note">GridPool is restarting. Wait a few seconds, then open the dashboard.</p>
}
else
{
<p class="safety-note">GridPool will remain in setup-only mode until it restarts. No mining work or peer relay is active yet.</p>
}
<a class="dashboard-button" href="/">Open GridPool dashboard</a>
}
else
{
Expand All @@ -77,9 +80,5 @@
}
</section>
</main>
@if (Model.AutomaticRestart)
{
<script src="/setup.js" defer></script>
}
</body>
</html>
2 changes: 2 additions & 0 deletions boot_portal/Pages/Setup.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public string NativeSv2Host

public int NativeSv2Port => _poolConfig.NativeSv2PublicPort;

public string NativeSv2AuthorityPublicKey => _poolConfig.NativeSv2AuthorityPublicKey;

public string NativeSv2Url => $"stratum2+noise://{NativeSv2Host}:{NativeSv2Port}";

public IActionResult OnGet()
Expand Down
7 changes: 7 additions & 0 deletions boot_portal/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,13 @@
{
config.TrustedPrivateDashboardEnabled = enabled;
}

string? nativeSv2AuthorityPublicKey =
Environment.GetEnvironmentVariable("GRIDPOOL_NATIVE_SV2_AUTHORITY_PUBLIC_KEY");
if (!string.IsNullOrWhiteSpace(nativeSv2AuthorityPublicKey))
{
config.NativeSv2AuthorityPublicKey = nativeSv2AuthorityPublicKey.Trim();
}
}

private static RateLimitPartition<string> CreateRateLimitPartition(HttpContext context, PoolConfig poolConfig, string policyName, int permitLimit)
Expand Down Expand Up @@ -1856,7 +1863,7 @@
// Truncate input to actual length
var cipherText = encryptedBody.AsSpan(0, bytesRead).ToArray();
byte[] combinedCiphertext = new byte[bytesRead + LibSodium.CryptoBox.NonceLen];
Array.Copy(_sessionNonceReceiver, 0, combinedCiphertext, 0, LibSodium.CryptoBox.NonceLen);

Check warning on line 1866 in boot_portal/Program.cs

View workflow job for this annotation

GitHub Actions / dotnet

Possible null reference argument for parameter 'sourceArray' in 'void Array.Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)'.

Check warning on line 1866 in boot_portal/Program.cs

View workflow job for this annotation

GitHub Actions / dotnet

Possible null reference argument for parameter 'sourceArray' in 'void Array.Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)'.

Check warning on line 1866 in boot_portal/Program.cs

View workflow job for this annotation

GitHub Actions / validate

Possible null reference argument for parameter 'sourceArray' in 'void Array.Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)'.
Array.Copy(encryptedBody, 0, combinedCiphertext, LibSodium.CryptoBox.NonceLen, bytesRead);


Expand Down
3 changes: 2 additions & 1 deletion boot_portal/Services/DashboardReadModelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ public DashboardSummaryDto BuildSummary(string? windowKey)
{
Enabled = _poolConfig.NativeSv2Enabled,
PublicHost = _poolConfig.NativeSv2PublicHost,
PublicPort = _poolConfig.NativeSv2PublicPort
PublicPort = _poolConfig.NativeSv2PublicPort,
AuthorityPublicKey = _poolConfig.NativeSv2AuthorityPublicKey
}
},
Capabilities = new DashboardCapabilitiesDto
Expand Down
1 change: 0 additions & 1 deletion boot_portal/Utils/NodeSetupPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ public static bool IsAllowedSetupPath(PathString path)
{
return path.StartsWithSegments("/setup", StringComparison.OrdinalIgnoreCase) ||
path.Equals("/setup.css", StringComparison.OrdinalIgnoreCase) ||
path.Equals("/setup.js", StringComparison.OrdinalIgnoreCase) ||
path.Equals("/health/live", StringComparison.OrdinalIgnoreCase) ||
path.Equals("/health/ready", StringComparison.OrdinalIgnoreCase);
}
Expand Down
1 change: 1 addition & 0 deletions boot_portal/boot_portal_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"native_sv2_enabled": false,
"native_sv2_public_host": "",
"native_sv2_public_port": 34265,
"native_sv2_authority_public_key": "",
"stratum_v1_proxy_host": "",
"stratum_v1_proxy_port": 0,
"grid_labs_support_fee_enabled": true,
Expand Down
3 changes: 2 additions & 1 deletion boot_portal/ui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ stay in React memory only and must not enter URLs, browser storage, logs, or
exports.

Miner-facing endpoints are explicit non-secret summary data. Packaged nodes set
`native_sv2_enabled`, `native_sv2_public_host`, and `native_sv2_public_port`;
`native_sv2_enabled`, `native_sv2_public_host`, `native_sv2_public_port`, and
`native_sv2_authority_public_key`;
when the host is blank the dashboard suggests the browser hostname and explains
that a reachable LAN hostname or IP may be substituted. Appliance wrappers may
explicitly set `trusted_private_dashboard_enabled` when their authenticated
Expand Down
1 change: 1 addition & 0 deletions boot_portal/ui/src/components/MinerConnection.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe("MinerConnectionPanel", () => {
render(<MinerConnectionPanel summary={summaryFixture} />);

expect(screen.getByText("stratum2+noise://node.gridpool.test:34265")).toBeInTheDocument();
expect(screen.getByText("9exampleAuthorityPublicKey")).toBeInTheDocument();
expect(screen.getByText("Your payout address, or a worker label")).toBeInTheDocument();
});

Expand Down
5 changes: 5 additions & 0 deletions boot_portal/ui/src/components/MinerConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const unavailableSv2 = {
enabled: false,
publicHost: "",
publicPort: 34265,
authorityPublicKey: "",
scheme: "stratum2+noise",
usernameGuidance: "Use a Bitcoin payout address or worker label."
};
Expand Down Expand Up @@ -49,10 +50,14 @@ export function MinerConnectionPanel({ summary }: { summary: DashboardSummary })
<dl className="connection-fields">
<div><dt>Host</dt><dd>{connectionHost(sv2.publicHost)}</dd></div>
<div><dt>Port</dt><dd>{sv2.publicPort}</dd></div>
<div><dt>Authority key</dt><dd><code>{sv2.authorityPublicKey || "Not advertised"}</code></dd></div>
<div><dt>Protocol</dt><dd>Native Stratum V2 with Noise</dd></div>
<div><dt>Username</dt><dd>Your payout address, or a worker label</dd></div>
</dl>
<p className="explain">{sv2.usernameGuidance}</p>
<p className="explain">
AxeOS users must paste the authority key into <strong>SV2 Authority Pubkey</strong> under the pool&apos;s advanced options.
</p>
<p className="explain">
The miner must be on a network that can reach this node. If the suggested
hostname does not resolve from the miner, use the Umbrel device&apos;s LAN IP.
Expand Down
1 change: 1 addition & 0 deletions boot_portal/ui/src/test/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const summaryFixture: DashboardSummary = {
enabled: true,
publicHost: "node.gridpool.test",
publicPort: 34265,
authorityPublicKey: "9exampleAuthorityPublicKey",
scheme: "stratum2+noise",
usernameGuidance: "Use a payout address or worker label."
}
Expand Down
1 change: 1 addition & 0 deletions boot_portal/ui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface DashboardSummary {
enabled: boolean;
publicHost: string;
publicPort: number;
authorityPublicKey: string;
scheme: string;
usernameGuidance: string;
};
Expand Down
15 changes: 15 additions & 0 deletions boot_portal/wwwroot/setup.css
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ button {

button:hover { transform: translateY(-1px); }

.dashboard-button {
display: block;
margin-top: 1rem;
border: 1px solid var(--line-strong);
border-radius: 3px;
padding: 0.9rem 1.1rem;
background: var(--text);
color: var(--bg);
font-weight: 650;
text-align: center;
text-decoration: none;
}

.dashboard-button:hover { transform: translateY(-1px); }

.field-validation,
.validation-summary { display: block; margin-top: 0.6rem; color: var(--bad); }

Expand Down
29 changes: 0 additions & 29 deletions boot_portal/wwwroot/setup.js

This file was deleted.

Loading