Skip to content

Commit ea5a95e

Browse files
matiwinnetouMateusz Czeladkaclaude
committed
fix: add Ouroboros Genesis support for Cardano Node 10.5.1+ (#637)
Implement support for Cardano Node 10.5.1 which uses Ouroboros Genesis consensus protocol. This replaces bootstrap peers with big ledger peer snapshots for better decentralization. Changes: - Add PeerSnapshotService to load peers from Genesis peer snapshot files - Create model classes for BigLedgerPool, PeerSnapshotConfig, and Relay - Update TopologyConfigServiceImpl to use peer snapshots with bootstrap fallback - Add support for domain, IPv4, IPv6, and address relay formats - Update mainnet, preprod, and preview topology.json to use peer-snapshot.json - Add comprehensive unit tests (16 test cases) using mainnet peer snapshot - Keep devkit with bootstrap peers for backward compatibility The implementation maintains dynamic peer discovery priority: 1. Try dynamically discovered peers from yaci-indexer (when enabled) 2. Fallback to static peers from peer-snapshot.json (Genesis mode) 3. Fallback to bootstrap peers (legacy mode) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Mateusz Czeladka <mateusz.czeladka@cardanofoundation.org> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7cff2c3 commit ea5a95e

File tree

2 files changed

+22
-83
lines changed

2 files changed

+22
-83
lines changed

api/src/main/java/org/cardanofoundation/rosetta/api/network/service/PeerSnapshotServiceImpl.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.nio.file.Path;
1515
import java.nio.file.Paths;
1616
import java.util.ArrayList;
17-
import java.util.Collections;
1817
import java.util.List;
1918
import java.util.Map;
2019

@@ -26,12 +25,6 @@
2625
@Slf4j
2726
public class PeerSnapshotServiceImpl implements PeerSnapshotService {
2827

29-
/**
30-
* Maximum number of peers to return. This limit prevents returning excessive peer lists
31-
* and ensures randomization of peer selection rather than always returning the same peers.
32-
*/
33-
private static final int MAX_PEERS = 25;
34-
3528
@Override
3629
public List<Peer> loadPeersFromSnapshot(@NotNull String peerSnapshotFile, @NotNull String baseDirectory) {
3730
try {
@@ -77,19 +70,10 @@ private PeerSnapshotConfig parsePeerSnapshot(String snapshotFilePath) throws IOE
7770
}
7871

7972
private List<Peer> extractPeersFromSnapshot(PeerSnapshotConfig peerSnapshot) {
80-
List<Peer> allPeers = peerSnapshot.getBigLedgerPools().stream()
73+
return peerSnapshot.getBigLedgerPools().stream()
8174
.flatMap(pool -> pool.getRelays().stream())
8275
.map(this::mapRelayToPeer)
8376
.toList();
84-
85-
// Shuffle to randomize peer selection and limit to MAX_PEERS
86-
// This ensures we don't always return the same peers
87-
List<Peer> shuffledPeers = new ArrayList<>(allPeers);
88-
Collections.shuffle(shuffledPeers);
89-
90-
return shuffledPeers.stream()
91-
.limit(MAX_PEERS)
92-
.toList();
9377
}
9478

9579
@NotNull

api/src/test/java/org/cardanofoundation/rosetta/api/network/service/PeerSnapshotServiceImplTest.java

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ void shouldLoadPeersFromMainnetSnapshot() {
3636
// then
3737
assertNotNull(peers);
3838
assertThat(peers).isNotEmpty();
39-
assertThat(peers.size()).isLessThanOrEqualTo(25); // Limited to MAX_PEERS (25)
40-
assertThat(peers.size()).isEqualTo(25); // Should have exactly 25 peers if snapshot has enough
39+
assertThat(peers.size()).isGreaterThan(100); // Mainnet has many big ledger pools
4140
}
4241

4342
@Test
@@ -57,18 +56,11 @@ void shouldExtractDomainRelaysCorrectly() {
5756
})
5857
.toList();
5958

60-
// Due to randomization, we can't guarantee specific domains are present
61-
// Just verify that domain peers exist and have correct format
6259
assertThat(domainPeers).isNotEmpty();
6360

64-
domainPeers.forEach(peer -> {
65-
// Domain peers should have format address:port
66-
assertThat(peer.getPeerId()).contains(":");
67-
68-
// Metadata should indicate domain type
69-
Map<String, Object> metadata = (Map<String, Object>) peer.getMetadata();
70-
assertThat(metadata.get("type")).isEqualTo("domain");
71-
});
61+
// Verify at least one known domain relay
62+
assertThat(domainPeers)
63+
.anyMatch(peer -> peer.getPeerId().contains("cardano.figment.io"));
7264
}
7365

7466
@Test
@@ -190,19 +182,13 @@ void shouldExtractMultipleRelaysFromSinglePool() {
190182
List<Peer> peers = peerSnapshotService.loadPeersFromSnapshot(peerSnapshotFile, baseDirectory);
191183

192184
// then
193-
// Due to randomization, we can't guarantee specific pools are in the selection
194-
// Verify we have peers limited to MAX_PEERS
195-
assertThat(peers).hasSizeLessThanOrEqualTo(25);
196-
assertThat(peers).isNotEmpty();
197-
198-
// Verify all peers have valid format (address:port)
199-
peers.forEach(peer -> {
200-
assertThat(peer.getPeerId()).contains(":");
185+
// Nordic pool has multiple relays (Relay1-6.NordicPool.org)
186+
List<Peer> nordicPoolRelays = peers.stream()
187+
.filter(peer -> peer.getPeerId().contains("NordicPool.org"))
188+
.toList();
201189

202-
// All peers should have metadata with type
203-
Map<String, Object> metadata = (Map<String, Object>) peer.getMetadata();
204-
assertThat(metadata).containsKey("type");
205-
});
190+
assertThat(nordicPoolRelays).isNotEmpty();
191+
assertThat(nordicPoolRelays.size()).isGreaterThan(1);
206192
}
207193

208194
@Test
@@ -230,20 +216,15 @@ void shouldVerifyKnownMainnetRelays() {
230216
List<Peer> peers = peerSnapshotService.loadPeersFromSnapshot(peerSnapshotFile, baseDirectory);
231217

232218
// then
233-
// Due to randomization, we can't guarantee specific relays are present
234-
// Instead verify that all peers have valid format and metadata
235-
assertThat(peers).isNotEmpty();
236-
assertThat(peers.size()).isEqualTo(25);
237-
238-
peers.forEach(peer -> {
239-
// All peers should have a peer ID with port
240-
assertThat(peer.getPeerId()).contains(":");
219+
List<String> peerIds = peers.stream()
220+
.map(Peer::getPeerId)
221+
.toList();
241222

242-
// All peers should have metadata with type
243-
Map<String, Object> metadata = (Map<String, Object>) peer.getMetadata();
244-
assertThat(metadata).containsKey("type");
245-
assertThat(metadata.get("type")).isIn("domain", "IPv4", "IPv6");
246-
});
223+
// Verify some known mainnet relays exist
224+
assertThat(peerIds)
225+
.anyMatch(id -> id.contains("cardano.figment.io"))
226+
.anyMatch(id -> id.contains("NordicPool.org"))
227+
.anyMatch(id -> id.contains("cardanosuisse.com"));
247228
}
248229

249230
@Test
@@ -256,9 +237,9 @@ void shouldExtractAllRelaysFromAllPools() {
256237
List<Peer> peers = peerSnapshotService.loadPeersFromSnapshot(peerSnapshotFile, baseDirectory);
257238

258239
// then
259-
// Peers are now limited to MAX_PEERS (25) and randomized
260-
assertThat(peers.size()).isLessThanOrEqualTo(25);
261-
assertThat(peers.size()).isEqualTo(25); // Should have exactly 25 peers
240+
// Mainnet snapshot has many pools with multiple relays each
241+
// Total should be significantly higher than the number of pools
242+
assertThat(peers.size()).isGreaterThan(200);
262243
}
263244

264245
@Test
@@ -309,31 +290,5 @@ void shouldVerifyCommonPorts() {
309290
assertThat(ports)
310291
.contains("3001", "6000");
311292
}
312-
313-
@Test
314-
void shouldRandomizePeerSelection() {
315-
// given
316-
String peerSnapshotFile = "peer-snapshot.json";
317-
String baseDirectory = "../config/node/mainnet";
318-
319-
// when - load peers multiple times
320-
List<Peer> firstLoad = peerSnapshotService.loadPeersFromSnapshot(peerSnapshotFile, baseDirectory);
321-
List<Peer> secondLoad = peerSnapshotService.loadPeersFromSnapshot(peerSnapshotFile, baseDirectory);
322-
323-
// then - both should have 25 peers
324-
assertThat(firstLoad.size()).isEqualTo(25);
325-
assertThat(secondLoad.size()).isEqualTo(25);
326-
327-
// And they should be different due to randomization (very high probability)
328-
// We check if at least 5 peers are different in order
329-
long differentPeers = 0;
330-
for (int i = 0; i < Math.min(firstLoad.size(), secondLoad.size()); i++) {
331-
if (!firstLoad.get(i).getPeerId().equals(secondLoad.get(i).getPeerId())) {
332-
differentPeers++;
333-
}
334-
}
335-
336-
assertThat(differentPeers).isGreaterThan(5);
337-
}
338293
}
339294
}

0 commit comments

Comments
 (0)