Skip to content

Commit

Permalink
feat: #110 New admin endpoint to create and start node with updated g…
Browse files Browse the repository at this point in the history
…enesis config
  • Loading branch information
satran004 committed Mar 5, 2025
1 parent 7b52c1c commit 0971cbe
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.bloxbean.cardano.yaci.core.protocol.localstate.api.Era;
import com.bloxbean.cardano.yaci.core.util.OSUtil;
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
import com.bloxbean.cardano.yacicli.localcluster.model.RunStatus;
import com.bloxbean.cardano.yacicli.localcluster.config.ApplicationConfig;
import com.bloxbean.cardano.yacicli.localcluster.config.GenesisConfig;
Expand Down Expand Up @@ -73,6 +74,9 @@ public class ClusterService {
@Autowired
private GenesisConfig genesisConfig;

@Autowired
private CustomGenesisConfig customGenesisConfig;

@Autowired
private PrivNetService privNetService;

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

GenesisConfig genesisConfigCopy = genesisConfig.copy();
genesisConfigCopy.merge(customGenesisConfig.getMap());

if (clusterInfo.getGenesisProfile() != null)
genesisConfigCopy = GenesisProfile.applyGenesisProfile(clusterInfo.getGenesisProfile(), genesisConfigCopy);

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

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

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

if (genesisConfig.getConwayHardForkAtEpoch() > 0 && genesisConfig.isShiftStartTimeBehind()) {
if (genesisConfigCopy.getConwayHardForkAtEpoch() > 0 && genesisConfigCopy.isShiftStartTimeBehind()) {
//Workaround for https://github.com/bloxbean/yaci-devkit/issues/65
//Calculate required securityParam to jump directly to epoch = 1
long expectedStabilityWindow = Math.round(epochLength * 1.5);
securityParam = Math.round(expectedStabilityWindow * activeSlotsCoeff) / 3;
} else {
if (securityParam == 0) {
//For stabilityWindow = epochLength * stabilityWindowFactory (0-1) , k = (epochLength * coefficient) / (3 * 2)
securityParam = Math.round(((epochLength * activeSlotsCoeff) / 3) * genesisConfig.getStabilityWindowFactor());
securityParam = Math.round(((epochLength * activeSlotsCoeff) / 3) * genesisConfigCopy.getStabilityWindowFactor());
}
}

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

Map values = genesisConfig.getConfigMap();
var genesisConfigCopy = genesisConfig.copy();
genesisConfigCopy.merge(customGenesisConfig.getMap());

Map values = genesisConfigCopy.getConfigMap();
values.put("enableP2P", String.valueOf(enableP2P));
values.put("peerSharing", genesisConfig.isPeerSharing());
values.put("peerSharing", genesisConfigCopy.isPeerSharing());
values.put("prometheusPort", String.valueOf(clusterInfo.getPrometheusPort()));
values.put("conway_era", clusterInfo.getEra() == Era.Conway ? Boolean.TRUE : Boolean.FALSE);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bloxbean.cardano.yacicli.localcluster;

import com.bloxbean.cardano.yaci.core.util.OSUtil;
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
import com.bloxbean.cardano.yacicli.localcluster.config.GenesisConfig;
import com.bloxbean.cardano.yacicli.localcluster.model.RunStatus;
import com.bloxbean.cardano.yacicli.util.PortUtil;
Expand Down Expand Up @@ -39,6 +40,7 @@ public class ClusterStartService {
private final ClusterPortInfoHelper clusterPortInfoHelper;
private final ProcessUtil processUtil;
private final GenesisConfig genesisConfig;
private final CustomGenesisConfig customGenesisConfig;

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

if (genesisConfig.getConwayHardForkAtEpoch() > 0 && genesisConfig.isShiftStartTimeBehind()) {
var genesisConfigCopy = genesisConfig.copy();
genesisConfigCopy.merge(customGenesisConfig.getMap());

if (genesisConfigCopy.getConwayHardForkAtEpoch() > 0 && genesisConfigCopy.isShiftStartTimeBehind()) {
long stabilityWindow = (long) Math.floor((3 * clusterInfo.getSecurityParam()) / clusterInfo.getActiveSlotsCoeff());

long maxBehindBySecond = stabilityWindow - 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.bloxbean.cardano.yacicli.localcluster.ClusterCommands;
import com.bloxbean.cardano.yacicli.localcluster.ClusterInfo;
import com.bloxbean.cardano.yacicli.localcluster.ClusterService;
import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig;
import com.bloxbean.cardano.yacicli.localcluster.service.ClusterUtilService;
import com.bloxbean.cardano.yacicli.common.CommandContext;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
Expand All @@ -21,6 +23,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

Expand All @@ -34,6 +37,7 @@ public class ClusterAdminController {
private final ClusterService clusterService;
private final ClusterUtilService clusterUtilService;
private final ClusterCommands clusterCommands;
private final CustomGenesisConfig customGenesisConfig;

@GetMapping("/devnet/download")
public ResponseEntity<InputStreamResource> downloadFiles() throws IOException {
Expand Down Expand Up @@ -170,4 +174,38 @@ public ResponseEntity<String> getGenesisHash() {
else
return ResponseEntity.notFound().build();
}

@PostMapping("/devnet/create")
@Operation(summary = """
Create and start the devnet by overriding the provided genesis properties. This method accepts a JSON object as input to
override the default genesis property values. All properties defined in `node.properties`, except for nested properties, are supported.
This is useful for dynamically changing the node configuration without modifying the `node.properties` file.
A value should always be set as string even if it's a number or boolean.
""")
public boolean create(@RequestBody Map<String, String> requestData) {
CommandContext.INSTANCE.setProperty("cluster_name", DEFAULT_CLUSTER_NAME);

int epochLength = 0;
try {
epochLength = Integer.parseInt(requestData.get("epochLength"));
} catch (Exception e) {
}

if (epochLength == 0)
epochLength = 600;

//Check if constitution script is there and it's value is empty
var constitutionScript = requestData.get("constitutionScript");
if (constitutionScript == null || constitutionScript.isEmpty())
requestData.remove(constitutionScript);

customGenesisConfig.populate(requestData);

try {
clusterCommands.createCluster(DEFAULT_CLUSTER_NAME, 3001, 8090, 1, 1, epochLength, true, true, "conway", null, false);
return true;
} catch (Exception e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.bloxbean.cardano.yacicli.localcluster.config;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/*
* This class holds the custom overloaded values for the GenesisConfig. Ideally, the map in this class remains empty.
* 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.
*/
@Component
public class CustomGenesisConfig {
private Map<String, String> map;

public CustomGenesisConfig() {
this.map = new HashMap<>();
}

public void populate(Map<String, String> updatedMap) {
this.map.clear();
this.map.putAll(updatedMap);
}

public Map<String, String> getMap() {
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public Map getConfigMap() {
map.put("minFeeRefScriptCostPerByte", minFeeRefScriptCostPerByte);
map.put("constitutionUrl", constitutionUrl);
map.put("constitutionDataHash", constitutionDataHash);
if (constitutionScript != null)
if (constitutionScript != null && !constitutionScript.trim().isEmpty())
map.put("constitutionScript", constitutionScript);

if (ccMembers != null && ccMembers.size() > 0)
Expand Down Expand Up @@ -448,6 +448,116 @@ public GenesisConfig copy() {
return genesisConfig;
}

public void merge(Map<String, String> updatedValues) {
if (updatedValues != null && !updatedValues.isEmpty()) {
if (updatedValues.get("protocolMagic") != null && !updatedValues.get("protocolMagic").isEmpty())
protocolMagic = Long.parseLong(updatedValues.get("protocolMagic"));
if (updatedValues.get("maxKESEvolutions") != null && !updatedValues.get("maxKESEvolutions").isEmpty())
maxKESEvolutions = Integer.parseInt(updatedValues.get("maxKESEvolutions"));
if (updatedValues.get("stabilityWindowFactor") != null && !updatedValues.get("stabilityWindowFactor").isEmpty())
stabilityWindowFactor = Integer.parseInt(updatedValues.get("stabilityWindowFactor"));
if (updatedValues.get("securityParam") != null && !updatedValues.get("securityParam").isEmpty())
securityParam = Integer.parseInt(updatedValues.get("securityParam"));
if (updatedValues.get("slotsPerKESPeriod") != null && !updatedValues.get("slotsPerKESPeriod").isEmpty())
slotsPerKESPeriod = Integer.parseInt(updatedValues.get("slotsPerKESPeriod"));
if (updatedValues.get("updateQuorum") != null && !updatedValues.get("updateQuorum").isEmpty())
updateQuorum = Integer.parseInt(updatedValues.get("updateQuorum"));
if (updatedValues.get("peerSharing") != null && !updatedValues.get("peerSharing").isEmpty())
peerSharing = Boolean.parseBoolean(updatedValues.get("peerSharing"));
if (updatedValues.get("genesisUtxoSupply") != null && !updatedValues.get("genesisUtxoSupply").isEmpty())
genesisUtxoSupply = updatedValues.get("genesisUtxoSupply");
if (updatedValues.get("nGenesisKeys") != null && !updatedValues.get("nGenesisKeys").isEmpty())
nGenesisKeys = Integer.parseInt(updatedValues.get("nGenesisKeys"));
if (updatedValues.get("nGenesisUtxoKeys") != null && !updatedValues.get("nGenesisUtxoKeys").isEmpty())
nGenesisUtxoKeys = Integer.parseInt(updatedValues.get("nGenesisUtxoKeys"));
if (updatedValues.get("maxLovelaceSupply") != null && !updatedValues.get("maxLovelaceSupply").isEmpty())
maxLovelaceSupply = updatedValues.get("maxLovelaceSupply");
if (updatedValues.get("poolPledgeInfluence") != null && !updatedValues.get("poolPledgeInfluence").isEmpty())
poolPledgeInfluence = Float.parseFloat(updatedValues.get("poolPledgeInfluence"));
if (updatedValues.get("decentralisationParam") != null && !updatedValues.get("decentralisationParam").isEmpty())
decentralisationParam = new BigDecimal(updatedValues.get("decentralisationParam"));
if (updatedValues.get("eMax") != null && !updatedValues.get("eMax").isEmpty())
eMax = Integer.parseInt(updatedValues.get("eMax"));
if (updatedValues.get("keyDeposit") != null && !updatedValues.get("keyDeposit").isEmpty())
keyDeposit = new BigInteger(updatedValues.get("keyDeposit"));
if (updatedValues.get("maxBlockBodySize") != null && !updatedValues.get("maxBlockBodySize").isEmpty())
maxBlockBodySize = Long.parseLong(updatedValues.get("maxBlockBodySize"));
if (updatedValues.get("maxBlockHeaderSize") != null && !updatedValues.get("maxBlockHeaderSize").isEmpty())
maxBlockHeaderSize = Long.parseLong(updatedValues.get("maxBlockHeaderSize"));
if (updatedValues.get("maxTxSize") != null && !updatedValues.get("maxTxSize").isEmpty())
maxTxSize = Long.parseLong(updatedValues.get("maxTxSize"));
if (updatedValues.get("minFeeA") != null && !updatedValues.get("minFeeA").isEmpty())
minFeeA = Long.parseLong(updatedValues.get("minFeeA"));
if (updatedValues.get("minFeeB") != null && !updatedValues.get("minFeeB").isEmpty())
minFeeB = Long.parseLong(updatedValues.get("minFeeB"));
if (updatedValues.get("minPoolCost") != null && !updatedValues.get("minPoolCost").isEmpty())
minPoolCost = new BigInteger(updatedValues.get("minPoolCost"));
if (updatedValues.get("minUTxOValue") != null && !updatedValues.get("minUTxOValue").isEmpty())
minUTxOValue = new BigInteger(updatedValues.get("minUTxOValue"));
if (updatedValues.get("nOpt") != null && !updatedValues.get("nOpt").isEmpty())
nOpt = Integer.parseInt(updatedValues.get("nOpt"));
if (updatedValues.get("poolDeposit") != null && !updatedValues.get("poolDeposit").isEmpty())
poolDeposit = new BigInteger(updatedValues.get("poolDeposit"));
if (updatedValues.get("protocolMajorVer") != null && !updatedValues.get("protocolMajorVer").isEmpty())
protocolMajorVer = Integer.parseInt(updatedValues.get("protocolMajorVer"));
if (updatedValues.get("protocolMinorVer") != null && !updatedValues.get("protocolMinorVer").isEmpty())
protocolMinorVer = Integer.parseInt(updatedValues.get("protocolMinorVer"));
if (updatedValues.get("monetaryExpansionRate") != null && !updatedValues.get("monetaryExpansionRate").isEmpty())
monetaryExpansionRate = Float.parseFloat(updatedValues.get("monetaryExpansionRate"));
if (updatedValues.get("treasuryGrowthRate") != null && !updatedValues.get("treasuryGrowthRate").isEmpty())
treasuryGrowthRate = Float.parseFloat(updatedValues.get("treasuryGrowthRate"));
if (updatedValues.get("collateralPercentage") != null && !updatedValues.get("collateralPercentage").isEmpty())
collateralPercentage = Integer.parseInt(updatedValues.get("collateralPercentage"));
if (updatedValues.get("prMemNumerator") != null && !updatedValues.get("prMemNumerator").isEmpty())
prMemNumerator = updatedValues.get("prMemNumerator");
if (updatedValues.get("prMemDenominator") != null && !updatedValues.get("prMemDenominator").isEmpty())
prMemDenominator = updatedValues.get("prMemDenominator");
if (updatedValues.get("prStepsNumerator") != null && !updatedValues.get("prStepsNumerator").isEmpty())
prStepsNumerator = updatedValues.get("prStepsNumerator");
if (updatedValues.get("prStepsDenominator") != null && !updatedValues.get("prStepsDenominator").isEmpty())
prStepsDenominator = updatedValues.get("prStepsDenominator");
if (updatedValues.get("lovelacePerUTxOWord") != null && !updatedValues.get("lovelacePerUTxOWord").isEmpty())
lovelacePerUTxOWord = Long.parseLong(updatedValues.get("lovelacePerUTxOWord"));
if (updatedValues.get("maxBlockExUnitsMem") != null && !updatedValues.get("maxBlockExUnitsMem").isEmpty())
maxBlockExUnitsMem = Long.parseLong(updatedValues.get("maxBlockExUnitsMem"));
if (updatedValues.get("maxBlockExUnitsSteps") != null && !updatedValues.get("maxBlockExUnitsSteps").isEmpty())
maxBlockExUnitsSteps = Long.parseLong(updatedValues.get("maxBlockExUnitsSteps"));
if (updatedValues.get("maxCollateralInputs") != null && !updatedValues.get("maxCollateralInputs").isEmpty())
maxCollateralInputs = Integer.parseInt(updatedValues.get("maxCollateralInputs"));
if (updatedValues.get("maxTxExUnitsMem") != null && !updatedValues.get("maxTxExUnitsMem").isEmpty())
maxTxExUnitsMem = Long.parseLong(updatedValues.get("maxTxExUnitsMem"));
if (updatedValues.get("maxTxExUnitsSteps") != null && !updatedValues.get("maxTxExUnitsSteps").isEmpty())
maxTxExUnitsSteps = Long.parseLong(updatedValues.get("maxTxExUnitsSteps"));
if (updatedValues.get("maxValueSize") != null && !updatedValues.get("maxValueSize").isEmpty())
maxValueSize = Integer.parseInt(updatedValues.get("maxValueSize"));
if (updatedValues.get("committeeMinSize") != null && !updatedValues.get("committeeMinSize").isEmpty())
committeeMinSize = Integer.parseInt(updatedValues.get("committeeMinSize"));
if (updatedValues.get("committeeMaxTermLength") != null && !updatedValues.get("committeeMaxTermLength").isEmpty())
committeeMaxTermLength = Integer.parseInt(updatedValues.get("committeeMaxTermLength"));
if (updatedValues.get("govActionLifetime") != null && !updatedValues.get("govActionLifetime").isEmpty())
govActionLifetime = Integer.parseInt(updatedValues.get("govActionLifetime"));
if (updatedValues.get("govActionDeposit") != null && !updatedValues.get("govActionDeposit").isEmpty())
govActionDeposit = new BigInteger(updatedValues.get("govActionDeposit"));
if (updatedValues.get("dRepDeposit") != null && !updatedValues.get("dRepDeposit").isEmpty())
dRepDeposit = new BigInteger(updatedValues.get("dRepDeposit"));
if (updatedValues.get("dRepActivity") != null && !updatedValues.get("dRepActivity").isEmpty())
dRepActivity = Integer.parseInt(updatedValues.get("dRepActivity"));
if (updatedValues.get("minFeeRefScriptCostPerByte") != null && !updatedValues.get("minFeeRefScriptCostPerByte").isEmpty())
minFeeRefScriptCostPerByte = new BigDecimal(updatedValues.get("minFeeRefScriptCostPerByte"));
if (updatedValues.get("constitutionUrl") != null && !updatedValues.get("constitutionUrl").isEmpty())
constitutionUrl = updatedValues.get("constitutionUrl");
if (updatedValues.get("constitutionDataHash") != null && !updatedValues.get("constitutionDataHash").isEmpty())
constitutionDataHash = updatedValues.get("constitutionDataHash");
if (updatedValues.get("constitutionScript") != null && !updatedValues.get("constitutionScript").trim().isEmpty())
constitutionScript = updatedValues.get("constitutionScript");

if (updatedValues.get("shiftStartTimeBehind") != null && !updatedValues.get("shiftStartTimeBehind").isEmpty())
shiftStartTimeBehind = Boolean.parseBoolean(updatedValues.get("shiftStartTimeBehind"));
if (updatedValues.get("conwayHardForkAtEpoch") != null && !updatedValues.get("conwayHardForkAtEpoch").isEmpty())
conwayHardForkAtEpoch = Integer.parseInt(updatedValues.get("conwayHardForkAtEpoch"));
}
}

private <K, V> List<MapItem<K, V>> createListWithLastFlag(Map<K, V> faucets) {
List<MapItem<K, V>> faucetList = new ArrayList<>();
int i = 0;
Expand Down
Loading

0 comments on commit 0971cbe

Please sign in to comment.