Skip to content

Commit 118d91e

Browse files
committed
refactor(bootstrap): Inject node bridge factories
Add a bootstrap-owned NodeRuntimeBridgeFactories holder for the\nlegacy admin and HTTP shell defaults, and thread it through\nNodeStarter into Node.createForBootstrap.\n\nKeep Node dependent on the seam factory types only, and extend\nfocused bootstrap tests to cover the new holder and injected\nconstruction path.
1 parent c08a7a2 commit 118d91e

6 files changed

Lines changed: 201 additions & 15 deletions

File tree

src/main/java/network/crypta/node/Node.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@
3838
import network.crypta.node.subsystem.NodeNetworkSubsystem;
3939
import network.crypta.node.subsystem.NodeRoutingSubsystem;
4040
import network.crypta.node.subsystem.NodeStorageSubsystem;
41+
import network.crypta.runtime.admin.AdminRuntimeBridgeInputsFactory;
4142
import network.crypta.runtime.bootstrap.NodeBootstrap;
4243
import network.crypta.runtime.bootstrap.NodeConfigManager;
44+
import network.crypta.runtime.bootstrap.NodeRuntimeBridgeFactories;
4345
import network.crypta.runtime.bootstrap.NodeStarter;
4446
import network.crypta.runtime.endpoints.NodeClientCoreInit;
45-
import network.crypta.runtime.endpoints.admin.AdminRuntimeBridgeInputsFactories;
4647
import network.crypta.runtime.endpoints.http.HttpShellContainer;
4748
import network.crypta.runtime.endpoints.http.HttpShellRuntimeSupportFactory;
4849
import network.crypta.runtime.services.NodeServicesSubsystem;
@@ -549,6 +550,7 @@ public NodeStarter getNodeStarter() {
549550
* @param weakRandom the fast random source, or {@code null} to derive one from the secure source
550551
* @param ns the associated starter, or {@code null} for test bootstrap
551552
* @param executor the executor used by the node runtime
553+
* @param runtimeBridgeFactories bootstrap-selected runtime bridge factories
552554
* @return a newly constructed node instance
553555
* @throws NodeInitException if the node initialization fails
554556
*/
@@ -557,9 +559,10 @@ public static Node createForBootstrap(
557559
RandomSource r,
558560
RandomSource weakRandom,
559561
NodeStarter ns,
560-
PriorityAwareExecutor executor)
562+
PriorityAwareExecutor executor,
563+
NodeRuntimeBridgeFactories runtimeBridgeFactories)
561564
throws NodeInitException {
562-
return new Node(config, r, weakRandom, ns, executor);
565+
return new Node(config, r, weakRandom, ns, executor, runtimeBridgeFactories);
563566
}
564567

565568
/**
@@ -573,15 +576,23 @@ public static Node createForBootstrap(
573576
* will be used, seeded from the secure PRNG.
574577
* @param ns NodeStarter
575578
* @param executor Executor
579+
* @param runtimeBridgeFactories Bootstrap-selected runtime bridge factories
576580
* @throws NodeInitException If the node initialization fails.
577581
*/
578582
Node(
579583
PersistentConfig config,
580584
RandomSource r,
581585
RandomSource weakRandom,
582586
NodeStarter ns,
583-
PriorityAwareExecutor executor)
587+
PriorityAwareExecutor executor,
588+
NodeRuntimeBridgeFactories runtimeBridgeFactories)
584589
throws NodeInitException {
590+
NodeRuntimeBridgeFactories nodeRuntimeBridgeFactories =
591+
Objects.requireNonNull(runtimeBridgeFactories, "runtimeBridgeFactories");
592+
AdminRuntimeBridgeInputsFactory adminRuntimeBridgeInputsFactory =
593+
nodeRuntimeBridgeFactories.adminRuntimeBridgeInputsFactory();
594+
HttpShellRuntimeSupportFactory httpShellRuntimeSupportFactory =
595+
nodeRuntimeBridgeFactories.httpShellRuntimeSupportFactory();
585596
this.executor = executor;
586597
this.nodeStarter = ns;
587598
this.messaging = new NodeMessagingSubsystem();
@@ -702,15 +713,15 @@ public static Node createForBootstrap(
702713
NodeClientCore clientCoreLocal =
703714
new NodeClientCore(
704715
this,
705-
AdminRuntimeBridgeInputsFactories.coreBacked(),
716+
adminRuntimeBridgeInputsFactory,
706717
clientCoreInit,
707718
network.darknetPortNumber(),
708719
sortOrder,
709720
storage.getDatabaseKey(),
710721
persistentSecret);
711722
services.setClientCore(clientCoreLocal);
712723
HttpShellContainer toadlets = services.toadlets();
713-
toadlets.setRuntimeSupport(HttpShellRuntimeSupportFactory.coreBacked().create(clientCoreLocal));
724+
toadlets.setRuntimeSupport(httpShellRuntimeSupportFactory.create(clientCoreLocal));
714725

715726
services.registerJvmVersionAlertIfNeeded();
716727

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package network.crypta.runtime.bootstrap;
2+
3+
import java.util.Objects;
4+
import network.crypta.runtime.admin.AdminRuntimeBridgeInputsFactory;
5+
import network.crypta.runtime.endpoints.admin.AdminRuntimeBridgeInputsFactories;
6+
import network.crypta.runtime.endpoints.http.HttpShellRuntimeSupportFactory;
7+
8+
/**
9+
* Bootstrap-owned selection of runtime bridge factories for {@link network.crypta.node.Node}.
10+
*
11+
* <p>This holder keeps the composition-root decision about which runtime bridge implementations to
12+
* use out of the node kernel. Bootstrap code selects the seam implementations once, then passes
13+
* them into {@code Node} so the node can stay focused on coordination and lifecycle rather than on
14+
* choosing endpoint-backed defaults.
15+
*
16+
* <p>Typical startup paths create one instance during bootstrap, thread it through {@link
17+
* network.crypta.runtime.bootstrap.NodeStarter}, and then let the node reuse those seams at the
18+
* same construction points where it previously hard-coded the legacy endpoint pair. The record is
19+
* immutable and carries no caching or policy beyond that wiring choice, so startup order and bridge
20+
* behavior stay aligned with the existing daemon bootstrap flow.
21+
*
22+
* @param adminRuntimeBridgeInputsFactory factory for the admin/runtime bridge inputs used by the
23+
* client core
24+
* @param httpShellRuntimeSupportFactory factory for HTTP shell runtime support used by the toadlet
25+
* container
26+
*/
27+
public record NodeRuntimeBridgeFactories(
28+
AdminRuntimeBridgeInputsFactory adminRuntimeBridgeInputsFactory,
29+
HttpShellRuntimeSupportFactory httpShellRuntimeSupportFactory) {
30+
31+
/**
32+
* Creates a bootstrap bridge-factory bundle.
33+
*
34+
* <p>Both factories are required because node construction expects explicit seams for the admin
35+
* runtime bridge inputs and the HTTP shell runtime support path. The constructor only validates
36+
* presence; it does not invoke the factories, cache runtime state, or trigger any endpoint
37+
* startup work on its own.
38+
*
39+
* @param adminRuntimeBridgeInputsFactory factory for admin bridge inputs passed into the client
40+
* core during node construction
41+
* @param httpShellRuntimeSupportFactory factory for HTTP shell runtime support created after the
42+
* client core exists
43+
* @throws NullPointerException if either factory reference is {@code null}
44+
*/
45+
public NodeRuntimeBridgeFactories {
46+
Objects.requireNonNull(adminRuntimeBridgeInputsFactory, "adminRuntimeBridgeInputsFactory");
47+
Objects.requireNonNull(httpShellRuntimeSupportFactory, "httpShellRuntimeSupportFactory");
48+
}
49+
50+
/**
51+
* Returns the legacy default bridge-factory pair backed by the current endpoint implementations.
52+
*
53+
* <p>This helper centralizes the existing production default in the bootstrap package. Callers
54+
* that need the historical daemon wiring can therefore get the same admin and HTTP shell bridge
55+
* choices without making the node kernel depend on the endpoint-owned static entry points.
56+
*
57+
* @return bridge factories that preserve the current core-backed admin and HTTP shell wiring
58+
*/
59+
public static NodeRuntimeBridgeFactories coreBacked() {
60+
return new NodeRuntimeBridgeFactories(
61+
AdminRuntimeBridgeInputsFactories.coreBacked(),
62+
HttpShellRuntimeSupportFactory.coreBacked());
63+
}
64+
}

src/main/java/network/crypta/runtime/bootstrap/NodeStarter.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,12 @@ public static Node createTestNode(TestNodeParameters params) throws NodeInitExce
306306

307307
Node node =
308308
Node.createForBootstrap(
309-
config, params.getRandom(), params.getRandom(), null, params.getExecutor());
309+
config,
310+
params.getRandom(),
311+
params.getRandom(),
312+
null,
313+
params.getExecutor(),
314+
NodeRuntimeBridgeFactories.coreBacked());
310315

311316
// All testing environments connect the nodes as they want, even if the old setup is restored,
312317
// it is not desired.
@@ -554,7 +559,9 @@ public Integer start(String[] args) {
554559
initSSL(cfg);
555560

556561
try {
557-
node = Node.createForBootstrap(cfg, null, null, this, executor);
562+
node =
563+
Node.createForBootstrap(
564+
cfg, null, null, this, executor, NodeRuntimeBridgeFactories.coreBacked());
558565
installSeednodesIfMissing(node);
559566
node.start(false);
560567
LOG.info("Node initialization completed");

src/main/java/network/crypta/runtime/bootstrap/package-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
*
44
* <p>This package groups the neutral runtime helpers that handle CLI parsing, startup logging
55
* configuration, the wrapper lifecycle bridge, program-directory setup, and early bootstrap/config
6-
* wiring during daemon startup. The classes remain part of the daemon runtime and preserve their
6+
* wiring during daemon startup. It also owns the default selection of runtime bridge factories
7+
* passed into the node kernel. The classes remain part of the daemon runtime and preserve their
78
* existing behavior, but they are not part of the routing, storage, or message kernel.
89
*/
910
package network.crypta.runtime.bootstrap;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package network.crypta.runtime.bootstrap;
2+
3+
import java.io.File;
4+
import network.crypta.clients.http.HttpShellRuntimeSupport;
5+
import network.crypta.node.Node;
6+
import network.crypta.node.NodeClientCore;
7+
import network.crypta.node.ProgramDirectory;
8+
import network.crypta.runtime.admin.AdminRuntimeBridgeInputs;
9+
import network.crypta.runtime.admin.AdminRuntimeBridgeInputsFactory;
10+
import network.crypta.runtime.endpoints.fcp.FcpQueueAdminBackend;
11+
import network.crypta.runtime.endpoints.http.CoreHttpShellRuntimeSupport;
12+
import network.crypta.runtime.endpoints.http.HttpShellRuntimeSupportFactory;
13+
import network.crypta.runtime.endpoints.http.geoip.HttpGeoIpCountryLookup;
14+
import org.junit.jupiter.api.Test;
15+
16+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
17+
import static org.junit.jupiter.api.Assertions.assertNotNull;
18+
import static org.junit.jupiter.api.Assertions.assertSame;
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.when;
22+
23+
@SuppressWarnings("java:S100")
24+
class NodeRuntimeBridgeFactoriesTest {
25+
26+
@Test
27+
void constructor_whenAdminRuntimeBridgeInputsFactoryIsNull_throws() {
28+
HttpShellRuntimeSupportFactory httpShellRuntimeSupportFactory =
29+
ignoredCore -> mock(HttpShellRuntimeSupport.class);
30+
31+
assertThrows(
32+
NullPointerException.class,
33+
() -> new NodeRuntimeBridgeFactories(null, httpShellRuntimeSupportFactory));
34+
}
35+
36+
@Test
37+
void constructor_whenHttpShellRuntimeSupportFactoryIsNull_throws() {
38+
NodeRuntimeBridgeFactoriesFixture fixture = new NodeRuntimeBridgeFactoriesFixture();
39+
AdminRuntimeBridgeInputsFactory adminRuntimeBridgeInputsFactory =
40+
fixture.adminRuntimeBridgeInputsFactory();
41+
42+
assertThrows(
43+
NullPointerException.class,
44+
() -> new NodeRuntimeBridgeFactories(adminRuntimeBridgeInputsFactory, null));
45+
}
46+
47+
@Test
48+
void coreBacked_whenCreated_exposesCurrentEndpointBackedFactories() {
49+
NodeRuntimeBridgeFactoriesFixture fixture = new NodeRuntimeBridgeFactoriesFixture();
50+
51+
NodeRuntimeBridgeFactories runtimeBridgeFactories = NodeRuntimeBridgeFactories.coreBacked();
52+
AdminRuntimeBridgeInputs bridgeInputs =
53+
runtimeBridgeFactories
54+
.adminRuntimeBridgeInputsFactory()
55+
.create(fixture.node(), fixture.core());
56+
HttpShellRuntimeSupport runtimeSupport =
57+
runtimeBridgeFactories.httpShellRuntimeSupportFactory().create(fixture.core());
58+
59+
assertNotNull(runtimeBridgeFactories.adminRuntimeBridgeInputsFactory());
60+
assertNotNull(runtimeBridgeFactories.httpShellRuntimeSupportFactory());
61+
assertInstanceOf(FcpQueueAdminBackend.class, bridgeInputs.queueAdminBackend());
62+
assertInstanceOf(HttpGeoIpCountryLookup.class, bridgeInputs.geoIpCountryLookup());
63+
CoreHttpShellRuntimeSupport coreRuntimeSupport =
64+
assertInstanceOf(CoreHttpShellRuntimeSupport.class, runtimeSupport);
65+
assertSame(fixture.core(), coreRuntimeSupport.core());
66+
}
67+
68+
private record NodeRuntimeBridgeFactoriesFixture(Node node, NodeClientCore core, File geoIpDb) {
69+
private NodeRuntimeBridgeFactoriesFixture() {
70+
this(mock(Node.class), mock(NodeClientCore.class), new File("run/IpToCountry.dat"));
71+
72+
ProgramDirectory runDir = mock(ProgramDirectory.class);
73+
when(node.runDir()).thenReturn(runDir);
74+
when(runDir.file("IpToCountry.dat")).thenReturn(geoIpDb);
75+
}
76+
77+
private AdminRuntimeBridgeInputsFactory adminRuntimeBridgeInputsFactory() {
78+
return (ignoredNode, ignoredCore) -> mock(AdminRuntimeBridgeInputs.class);
79+
}
80+
}
81+
}

src/test/java/network/crypta/runtime/bootstrap/NodeStarterTest.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.ArrayList;
88
import java.util.List;
99
import java.util.Properties;
10+
import java.util.concurrent.atomic.AtomicReference;
1011
import network.crypta.config.PersistentConfig;
1112
import network.crypta.config.SubConfig;
1213
import network.crypta.crypt.CryptoRandoms;
@@ -94,8 +95,9 @@ void createTestNode_whenCalled_constructsNodeWithPersistentConfigAndCleansPeers(
9495
params.setExecutor(mock(network.crypta.support.PriorityAwareExecutor.class));
9596

9697
// Capture constructor arguments and provide a mock PeerManager for getPeers()
97-
java.util.concurrent.atomic.AtomicReference<PersistentConfig> capturedCfg =
98-
new java.util.concurrent.atomic.AtomicReference<>();
98+
AtomicReference<PersistentConfig> capturedCfg = new AtomicReference<>();
99+
AtomicReference<NodeRuntimeBridgeFactories> capturedRuntimeBridgeFactories =
100+
new AtomicReference<>();
99101

100102
try (MockedConstruction<Node> mocked =
101103
Mockito.mockConstruction(
@@ -106,6 +108,10 @@ void createTestNode_whenCalled_constructsNodeWithPersistentConfigAndCleansPeers(
106108
if (arg0 instanceof PersistentConfig pc) {
107109
capturedCfg.set(pc);
108110
}
111+
Object arg5 = context.arguments().get(5);
112+
if (arg5 instanceof NodeRuntimeBridgeFactories runtimeBridgeFactories) {
113+
capturedRuntimeBridgeFactories.set(runtimeBridgeFactories);
114+
}
109115
// getPeers() must not return null because createTestNode() calls removeAllPeers()
110116
PeerManager peerManager = mock(PeerManager.class);
111117
NodeNetworkSubsystem network = mock(NodeNetworkSubsystem.class);
@@ -131,6 +137,7 @@ void createTestNode_whenCalled_constructsNodeWithPersistentConfigAndCleansPeers(
131137
// Per-port derived paths exist under base/port
132138
File portDir = new File(tmpDir, "12345");
133139
assertEquals(new File(portDir, "throttle.dat").toString(), sfs.get("node.throttleFile"));
140+
assertValidRuntimeBridgeFactories(capturedRuntimeBridgeFactories.get());
134141

135142
// And ensure the peer list is cleared on the returned node
136143
verify(node.network().peers(), times(1)).removeAllPeers();
@@ -144,23 +151,31 @@ void start_whenNodeStartupThrowsNodeInitException_andWrapperControlled_returnsEx
144151
NodeStarter ns = newNodeStarterViaReflection();
145152
int expectedExitCode = NodeInitException.EXIT_COULD_NOT_START_UPDATER;
146153
String[] args = startupArgs(tmpDir);
154+
AtomicReference<NodeRuntimeBridgeFactories> capturedRuntimeBridgeFactories =
155+
new AtomicReference<>();
147156

148157
try (MockedStatic<WrapperManager> wm = Mockito.mockStatic(WrapperManager.class);
149158
MockedConstruction<NativeThread> nativeThreadCtor =
150159
Mockito.mockConstruction(NativeThread.class);
151160
MockedConstruction<Node> nodeCtor =
152161
Mockito.mockConstruction(
153162
Node.class,
154-
(mock, _) ->
155-
Mockito.doThrow(new NodeInitException(expectedExitCode, "simulated startup"))
156-
.when(mock)
157-
.start(false))) {
163+
(mock, context) -> {
164+
Object arg5 = context.arguments().get(5);
165+
if (arg5 instanceof NodeRuntimeBridgeFactories runtimeBridgeFactories) {
166+
capturedRuntimeBridgeFactories.set(runtimeBridgeFactories);
167+
}
168+
Mockito.doThrow(new NodeInitException(expectedExitCode, "simulated startup"))
169+
.when(mock)
170+
.start(false);
171+
})) {
158172
wm.when(WrapperManager::isControlledByNativeWrapper).thenReturn(true);
159173
Integer result = ns.start(args);
160174

161175
assertEquals(expectedExitCode, result);
162176
assertEquals(1, nodeCtor.constructed().size());
163177
assertEquals(1, nativeThreadCtor.constructed().size());
178+
assertValidRuntimeBridgeFactories(capturedRuntimeBridgeFactories.get());
164179
wm.verify(() -> WrapperManager.signalStarting(500000), times(1));
165180
wm.verify(() -> WrapperManager.stop(expectedExitCode), times(0));
166181
}
@@ -487,4 +502,11 @@ private static File createStandaloneTempDir(String prefix) throws java.io.IOExce
487502
dir.deleteOnExit();
488503
return dir;
489504
}
505+
506+
private static void assertValidRuntimeBridgeFactories(
507+
NodeRuntimeBridgeFactories runtimeBridgeFactories) {
508+
assertNotNull(runtimeBridgeFactories);
509+
assertNotNull(runtimeBridgeFactories.adminRuntimeBridgeInputsFactory());
510+
assertNotNull(runtimeBridgeFactories.httpShellRuntimeSupportFactory());
511+
}
490512
}

0 commit comments

Comments
 (0)