Skip to content

Commit 0971cbe

Browse files
committed
feat: #110 New admin endpoint to create and start node with updated genesis config
1 parent 7b52c1c commit 0971cbe

File tree

6 files changed

+202
-7
lines changed

6 files changed

+202
-7
lines changed

applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterService.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.bloxbean.cardano.yaci.core.protocol.localstate.api.Era;
44
import com.bloxbean.cardano.yaci.core.util.OSUtil;
5+
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
56
import com.bloxbean.cardano.yacicli.localcluster.model.RunStatus;
67
import com.bloxbean.cardano.yacicli.localcluster.config.ApplicationConfig;
78
import com.bloxbean.cardano.yacicli.localcluster.config.GenesisConfig;
@@ -73,6 +74,9 @@ public class ClusterService {
7374
@Autowired
7475
private GenesisConfig genesisConfig;
7576

77+
@Autowired
78+
private CustomGenesisConfig customGenesisConfig;
79+
7680
@Autowired
7781
private PrivNetService privNetService;
7882

@@ -304,6 +308,8 @@ private void updateGenesis(Path clusterFolder, String clusterName, ClusterInfo c
304308
Path destConwayGenesisFile = clusterFolder.resolve("node").resolve("genesis").resolve("conway-genesis.json");
305309

306310
GenesisConfig genesisConfigCopy = genesisConfig.copy();
311+
genesisConfigCopy.merge(customGenesisConfig.getMap());
312+
307313
if (clusterInfo.getGenesisProfile() != null)
308314
genesisConfigCopy = GenesisProfile.applyGenesisProfile(clusterInfo.getGenesisProfile(), genesisConfigCopy);
309315

@@ -324,23 +330,23 @@ private void updateGenesis(Path clusterFolder, String clusterName, ClusterInfo c
324330
values.put("epochLength", String.valueOf(epochLength));
325331

326332
//Check if protocol version should be minimun 9 and it's conway era
327-
if (era == Era.Conway && genesisConfig.getProtocolMajorVer() < 9) {
333+
if (era == Era.Conway && genesisConfigCopy.getProtocolMajorVer() < 9) {
328334
values.put("protocolMajorVer", 10);
329335
values.put("protocolMinorVer", 2);
330336
}
331337

332338
//Derive security param
333339
long securityParam = genesisConfigCopy.getSecurityParam();
334340

335-
if (genesisConfig.getConwayHardForkAtEpoch() > 0 && genesisConfig.isShiftStartTimeBehind()) {
341+
if (genesisConfigCopy.getConwayHardForkAtEpoch() > 0 && genesisConfigCopy.isShiftStartTimeBehind()) {
336342
//Workaround for https://github.com/bloxbean/yaci-devkit/issues/65
337343
//Calculate required securityParam to jump directly to epoch = 1
338344
long expectedStabilityWindow = Math.round(epochLength * 1.5);
339345
securityParam = Math.round(expectedStabilityWindow * activeSlotsCoeff) / 3;
340346
} else {
341347
if (securityParam == 0) {
342348
//For stabilityWindow = epochLength * stabilityWindowFactory (0-1) , k = (epochLength * coefficient) / (3 * 2)
343-
securityParam = Math.round(((epochLength * activeSlotsCoeff) / 3) * genesisConfig.getStabilityWindowFactor());
349+
securityParam = Math.round(((epochLength * activeSlotsCoeff) / 3) * genesisConfigCopy.getStabilityWindowFactor());
344350
}
345351
}
346352

@@ -416,9 +422,12 @@ private void updateConfiguration(Path clusterFolder, ClusterInfo clusterInfo, Co
416422
Path destConfigPath = clusterFolder.resolve("node").resolve("configuration.yaml");
417423
boolean enableP2P = clusterInfo.isP2pEnabled();
418424

419-
Map values = genesisConfig.getConfigMap();
425+
var genesisConfigCopy = genesisConfig.copy();
426+
genesisConfigCopy.merge(customGenesisConfig.getMap());
427+
428+
Map values = genesisConfigCopy.getConfigMap();
420429
values.put("enableP2P", String.valueOf(enableP2P));
421-
values.put("peerSharing", genesisConfig.isPeerSharing());
430+
values.put("peerSharing", genesisConfigCopy.isPeerSharing());
422431
values.put("prometheusPort", String.valueOf(clusterInfo.getPrometheusPort()));
423432
values.put("conway_era", clusterInfo.getEra() == Era.Conway ? Boolean.TRUE : Boolean.FALSE);
424433

applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterStartService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.bloxbean.cardano.yacicli.localcluster;
22

33
import com.bloxbean.cardano.yaci.core.util.OSUtil;
4+
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
45
import com.bloxbean.cardano.yacicli.localcluster.config.GenesisConfig;
56
import com.bloxbean.cardano.yacicli.localcluster.model.RunStatus;
67
import com.bloxbean.cardano.yacicli.util.PortUtil;
@@ -39,6 +40,7 @@ public class ClusterStartService {
3940
private final ClusterPortInfoHelper clusterPortInfoHelper;
4041
private final ProcessUtil processUtil;
4142
private final GenesisConfig genesisConfig;
43+
private final CustomGenesisConfig customGenesisConfig;
4244

4345
private ObjectMapper objectMapper = new ObjectMapper();
4446
private List<Process> processes = new ArrayList<>();
@@ -263,7 +265,10 @@ private boolean setupFirstRun(ClusterInfo clusterInfo, Path clusterFolder, Consu
263265
ObjectNode jsonNode = (ObjectNode) objectMapper.readTree(byronGenesis.toFile());
264266
long byronStartTime = Instant.now().getEpochSecond();
265267

266-
if (genesisConfig.getConwayHardForkAtEpoch() > 0 && genesisConfig.isShiftStartTimeBehind()) {
268+
var genesisConfigCopy = genesisConfig.copy();
269+
genesisConfigCopy.merge(customGenesisConfig.getMap());
270+
271+
if (genesisConfigCopy.getConwayHardForkAtEpoch() > 0 && genesisConfigCopy.isShiftStartTimeBehind()) {
267272
long stabilityWindow = (long) Math.floor((3 * clusterInfo.getSecurityParam()) / clusterInfo.getActiveSlotsCoeff());
268273

269274
long maxBehindBySecond = stabilityWindow - 5;

applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import com.bloxbean.cardano.yacicli.localcluster.ClusterCommands;
44
import com.bloxbean.cardano.yacicli.localcluster.ClusterInfo;
55
import com.bloxbean.cardano.yacicli.localcluster.ClusterService;
6+
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
67
import com.bloxbean.cardano.yacicli.localcluster.service.ClusterUtilService;
78
import com.bloxbean.cardano.yacicli.common.CommandContext;
9+
import io.swagger.v3.oas.annotations.Operation;
810
import lombok.RequiredArgsConstructor;
911
import lombok.extern.slf4j.Slf4j;
1012
import org.apache.commons.io.FileUtils;
@@ -21,6 +23,7 @@
2123
import java.nio.file.Files;
2224
import java.nio.file.Path;
2325
import java.nio.file.Paths;
26+
import java.util.Map;
2427
import java.util.zip.ZipEntry;
2528
import java.util.zip.ZipOutputStream;
2629

@@ -34,6 +37,7 @@ public class ClusterAdminController {
3437
private final ClusterService clusterService;
3538
private final ClusterUtilService clusterUtilService;
3639
private final ClusterCommands clusterCommands;
40+
private final CustomGenesisConfig customGenesisConfig;
3741

3842
@GetMapping("/devnet/download")
3943
public ResponseEntity<InputStreamResource> downloadFiles() throws IOException {
@@ -170,4 +174,38 @@ public ResponseEntity<String> getGenesisHash() {
170174
else
171175
return ResponseEntity.notFound().build();
172176
}
177+
178+
@PostMapping("/devnet/create")
179+
@Operation(summary = """
180+
Create and start the devnet by overriding the provided genesis properties. This method accepts a JSON object as input to
181+
override the default genesis property values. All properties defined in `node.properties`, except for nested properties, are supported.
182+
This is useful for dynamically changing the node configuration without modifying the `node.properties` file.
183+
A value should always be set as string even if it's a number or boolean.
184+
""")
185+
public boolean create(@RequestBody Map<String, String> requestData) {
186+
CommandContext.INSTANCE.setProperty("cluster_name", DEFAULT_CLUSTER_NAME);
187+
188+
int epochLength = 0;
189+
try {
190+
epochLength = Integer.parseInt(requestData.get("epochLength"));
191+
} catch (Exception e) {
192+
}
193+
194+
if (epochLength == 0)
195+
epochLength = 600;
196+
197+
//Check if constitution script is there and it's value is empty
198+
var constitutionScript = requestData.get("constitutionScript");
199+
if (constitutionScript == null || constitutionScript.isEmpty())
200+
requestData.remove(constitutionScript);
201+
202+
customGenesisConfig.populate(requestData);
203+
204+
try {
205+
clusterCommands.createCluster(DEFAULT_CLUSTER_NAME, 3001, 8090, 1, 1, epochLength, true, true, "conway", null, false);
206+
return true;
207+
} catch (Exception e) {
208+
return false;
209+
}
210+
}
173211
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.bloxbean.cardano.yacicli.localcluster.config;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
/*
9+
* This class holds the custom overloaded values for the GenesisConfig. Ideally, the map in this class remains empty.
10+
* However, if the GenesisConfig is updated through the admin create endpoint, these values will be merged with the default GenesisConfig values during creation of node.
11+
*/
12+
@Component
13+
public class CustomGenesisConfig {
14+
private Map<String, String> map;
15+
16+
public CustomGenesisConfig() {
17+
this.map = new HashMap<>();
18+
}
19+
20+
public void populate(Map<String, String> updatedMap) {
21+
this.map.clear();
22+
this.map.putAll(updatedMap);
23+
}
24+
25+
public Map<String, String> getMap() {
26+
return map;
27+
}
28+
}

applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/config/GenesisConfig.java

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ public Map getConfigMap() {
335335
map.put("minFeeRefScriptCostPerByte", minFeeRefScriptCostPerByte);
336336
map.put("constitutionUrl", constitutionUrl);
337337
map.put("constitutionDataHash", constitutionDataHash);
338-
if (constitutionScript != null)
338+
if (constitutionScript != null && !constitutionScript.trim().isEmpty())
339339
map.put("constitutionScript", constitutionScript);
340340

341341
if (ccMembers != null && ccMembers.size() > 0)
@@ -448,6 +448,116 @@ public GenesisConfig copy() {
448448
return genesisConfig;
449449
}
450450

451+
public void merge(Map<String, String> updatedValues) {
452+
if (updatedValues != null && !updatedValues.isEmpty()) {
453+
if (updatedValues.get("protocolMagic") != null && !updatedValues.get("protocolMagic").isEmpty())
454+
protocolMagic = Long.parseLong(updatedValues.get("protocolMagic"));
455+
if (updatedValues.get("maxKESEvolutions") != null && !updatedValues.get("maxKESEvolutions").isEmpty())
456+
maxKESEvolutions = Integer.parseInt(updatedValues.get("maxKESEvolutions"));
457+
if (updatedValues.get("stabilityWindowFactor") != null && !updatedValues.get("stabilityWindowFactor").isEmpty())
458+
stabilityWindowFactor = Integer.parseInt(updatedValues.get("stabilityWindowFactor"));
459+
if (updatedValues.get("securityParam") != null && !updatedValues.get("securityParam").isEmpty())
460+
securityParam = Integer.parseInt(updatedValues.get("securityParam"));
461+
if (updatedValues.get("slotsPerKESPeriod") != null && !updatedValues.get("slotsPerKESPeriod").isEmpty())
462+
slotsPerKESPeriod = Integer.parseInt(updatedValues.get("slotsPerKESPeriod"));
463+
if (updatedValues.get("updateQuorum") != null && !updatedValues.get("updateQuorum").isEmpty())
464+
updateQuorum = Integer.parseInt(updatedValues.get("updateQuorum"));
465+
if (updatedValues.get("peerSharing") != null && !updatedValues.get("peerSharing").isEmpty())
466+
peerSharing = Boolean.parseBoolean(updatedValues.get("peerSharing"));
467+
if (updatedValues.get("genesisUtxoSupply") != null && !updatedValues.get("genesisUtxoSupply").isEmpty())
468+
genesisUtxoSupply = updatedValues.get("genesisUtxoSupply");
469+
if (updatedValues.get("nGenesisKeys") != null && !updatedValues.get("nGenesisKeys").isEmpty())
470+
nGenesisKeys = Integer.parseInt(updatedValues.get("nGenesisKeys"));
471+
if (updatedValues.get("nGenesisUtxoKeys") != null && !updatedValues.get("nGenesisUtxoKeys").isEmpty())
472+
nGenesisUtxoKeys = Integer.parseInt(updatedValues.get("nGenesisUtxoKeys"));
473+
if (updatedValues.get("maxLovelaceSupply") != null && !updatedValues.get("maxLovelaceSupply").isEmpty())
474+
maxLovelaceSupply = updatedValues.get("maxLovelaceSupply");
475+
if (updatedValues.get("poolPledgeInfluence") != null && !updatedValues.get("poolPledgeInfluence").isEmpty())
476+
poolPledgeInfluence = Float.parseFloat(updatedValues.get("poolPledgeInfluence"));
477+
if (updatedValues.get("decentralisationParam") != null && !updatedValues.get("decentralisationParam").isEmpty())
478+
decentralisationParam = new BigDecimal(updatedValues.get("decentralisationParam"));
479+
if (updatedValues.get("eMax") != null && !updatedValues.get("eMax").isEmpty())
480+
eMax = Integer.parseInt(updatedValues.get("eMax"));
481+
if (updatedValues.get("keyDeposit") != null && !updatedValues.get("keyDeposit").isEmpty())
482+
keyDeposit = new BigInteger(updatedValues.get("keyDeposit"));
483+
if (updatedValues.get("maxBlockBodySize") != null && !updatedValues.get("maxBlockBodySize").isEmpty())
484+
maxBlockBodySize = Long.parseLong(updatedValues.get("maxBlockBodySize"));
485+
if (updatedValues.get("maxBlockHeaderSize") != null && !updatedValues.get("maxBlockHeaderSize").isEmpty())
486+
maxBlockHeaderSize = Long.parseLong(updatedValues.get("maxBlockHeaderSize"));
487+
if (updatedValues.get("maxTxSize") != null && !updatedValues.get("maxTxSize").isEmpty())
488+
maxTxSize = Long.parseLong(updatedValues.get("maxTxSize"));
489+
if (updatedValues.get("minFeeA") != null && !updatedValues.get("minFeeA").isEmpty())
490+
minFeeA = Long.parseLong(updatedValues.get("minFeeA"));
491+
if (updatedValues.get("minFeeB") != null && !updatedValues.get("minFeeB").isEmpty())
492+
minFeeB = Long.parseLong(updatedValues.get("minFeeB"));
493+
if (updatedValues.get("minPoolCost") != null && !updatedValues.get("minPoolCost").isEmpty())
494+
minPoolCost = new BigInteger(updatedValues.get("minPoolCost"));
495+
if (updatedValues.get("minUTxOValue") != null && !updatedValues.get("minUTxOValue").isEmpty())
496+
minUTxOValue = new BigInteger(updatedValues.get("minUTxOValue"));
497+
if (updatedValues.get("nOpt") != null && !updatedValues.get("nOpt").isEmpty())
498+
nOpt = Integer.parseInt(updatedValues.get("nOpt"));
499+
if (updatedValues.get("poolDeposit") != null && !updatedValues.get("poolDeposit").isEmpty())
500+
poolDeposit = new BigInteger(updatedValues.get("poolDeposit"));
501+
if (updatedValues.get("protocolMajorVer") != null && !updatedValues.get("protocolMajorVer").isEmpty())
502+
protocolMajorVer = Integer.parseInt(updatedValues.get("protocolMajorVer"));
503+
if (updatedValues.get("protocolMinorVer") != null && !updatedValues.get("protocolMinorVer").isEmpty())
504+
protocolMinorVer = Integer.parseInt(updatedValues.get("protocolMinorVer"));
505+
if (updatedValues.get("monetaryExpansionRate") != null && !updatedValues.get("monetaryExpansionRate").isEmpty())
506+
monetaryExpansionRate = Float.parseFloat(updatedValues.get("monetaryExpansionRate"));
507+
if (updatedValues.get("treasuryGrowthRate") != null && !updatedValues.get("treasuryGrowthRate").isEmpty())
508+
treasuryGrowthRate = Float.parseFloat(updatedValues.get("treasuryGrowthRate"));
509+
if (updatedValues.get("collateralPercentage") != null && !updatedValues.get("collateralPercentage").isEmpty())
510+
collateralPercentage = Integer.parseInt(updatedValues.get("collateralPercentage"));
511+
if (updatedValues.get("prMemNumerator") != null && !updatedValues.get("prMemNumerator").isEmpty())
512+
prMemNumerator = updatedValues.get("prMemNumerator");
513+
if (updatedValues.get("prMemDenominator") != null && !updatedValues.get("prMemDenominator").isEmpty())
514+
prMemDenominator = updatedValues.get("prMemDenominator");
515+
if (updatedValues.get("prStepsNumerator") != null && !updatedValues.get("prStepsNumerator").isEmpty())
516+
prStepsNumerator = updatedValues.get("prStepsNumerator");
517+
if (updatedValues.get("prStepsDenominator") != null && !updatedValues.get("prStepsDenominator").isEmpty())
518+
prStepsDenominator = updatedValues.get("prStepsDenominator");
519+
if (updatedValues.get("lovelacePerUTxOWord") != null && !updatedValues.get("lovelacePerUTxOWord").isEmpty())
520+
lovelacePerUTxOWord = Long.parseLong(updatedValues.get("lovelacePerUTxOWord"));
521+
if (updatedValues.get("maxBlockExUnitsMem") != null && !updatedValues.get("maxBlockExUnitsMem").isEmpty())
522+
maxBlockExUnitsMem = Long.parseLong(updatedValues.get("maxBlockExUnitsMem"));
523+
if (updatedValues.get("maxBlockExUnitsSteps") != null && !updatedValues.get("maxBlockExUnitsSteps").isEmpty())
524+
maxBlockExUnitsSteps = Long.parseLong(updatedValues.get("maxBlockExUnitsSteps"));
525+
if (updatedValues.get("maxCollateralInputs") != null && !updatedValues.get("maxCollateralInputs").isEmpty())
526+
maxCollateralInputs = Integer.parseInt(updatedValues.get("maxCollateralInputs"));
527+
if (updatedValues.get("maxTxExUnitsMem") != null && !updatedValues.get("maxTxExUnitsMem").isEmpty())
528+
maxTxExUnitsMem = Long.parseLong(updatedValues.get("maxTxExUnitsMem"));
529+
if (updatedValues.get("maxTxExUnitsSteps") != null && !updatedValues.get("maxTxExUnitsSteps").isEmpty())
530+
maxTxExUnitsSteps = Long.parseLong(updatedValues.get("maxTxExUnitsSteps"));
531+
if (updatedValues.get("maxValueSize") != null && !updatedValues.get("maxValueSize").isEmpty())
532+
maxValueSize = Integer.parseInt(updatedValues.get("maxValueSize"));
533+
if (updatedValues.get("committeeMinSize") != null && !updatedValues.get("committeeMinSize").isEmpty())
534+
committeeMinSize = Integer.parseInt(updatedValues.get("committeeMinSize"));
535+
if (updatedValues.get("committeeMaxTermLength") != null && !updatedValues.get("committeeMaxTermLength").isEmpty())
536+
committeeMaxTermLength = Integer.parseInt(updatedValues.get("committeeMaxTermLength"));
537+
if (updatedValues.get("govActionLifetime") != null && !updatedValues.get("govActionLifetime").isEmpty())
538+
govActionLifetime = Integer.parseInt(updatedValues.get("govActionLifetime"));
539+
if (updatedValues.get("govActionDeposit") != null && !updatedValues.get("govActionDeposit").isEmpty())
540+
govActionDeposit = new BigInteger(updatedValues.get("govActionDeposit"));
541+
if (updatedValues.get("dRepDeposit") != null && !updatedValues.get("dRepDeposit").isEmpty())
542+
dRepDeposit = new BigInteger(updatedValues.get("dRepDeposit"));
543+
if (updatedValues.get("dRepActivity") != null && !updatedValues.get("dRepActivity").isEmpty())
544+
dRepActivity = Integer.parseInt(updatedValues.get("dRepActivity"));
545+
if (updatedValues.get("minFeeRefScriptCostPerByte") != null && !updatedValues.get("minFeeRefScriptCostPerByte").isEmpty())
546+
minFeeRefScriptCostPerByte = new BigDecimal(updatedValues.get("minFeeRefScriptCostPerByte"));
547+
if (updatedValues.get("constitutionUrl") != null && !updatedValues.get("constitutionUrl").isEmpty())
548+
constitutionUrl = updatedValues.get("constitutionUrl");
549+
if (updatedValues.get("constitutionDataHash") != null && !updatedValues.get("constitutionDataHash").isEmpty())
550+
constitutionDataHash = updatedValues.get("constitutionDataHash");
551+
if (updatedValues.get("constitutionScript") != null && !updatedValues.get("constitutionScript").trim().isEmpty())
552+
constitutionScript = updatedValues.get("constitutionScript");
553+
554+
if (updatedValues.get("shiftStartTimeBehind") != null && !updatedValues.get("shiftStartTimeBehind").isEmpty())
555+
shiftStartTimeBehind = Boolean.parseBoolean(updatedValues.get("shiftStartTimeBehind"));
556+
if (updatedValues.get("conwayHardForkAtEpoch") != null && !updatedValues.get("conwayHardForkAtEpoch").isEmpty())
557+
conwayHardForkAtEpoch = Integer.parseInt(updatedValues.get("conwayHardForkAtEpoch"));
558+
}
559+
}
560+
451561
private <K, V> List<MapItem<K, V>> createListWithLastFlag(Map<K, V> faucets) {
452562
List<MapItem<K, V>> faucetList = new ArrayList<>();
453563
int i = 0;

0 commit comments

Comments
 (0)