diff --git a/io-hotmoka-nodes-api/src/main/java/io/hotmoka/nodes/api/ConsensusConfig.java b/io-hotmoka-nodes-api/src/main/java/io/hotmoka/nodes/api/ConsensusConfig.java new file mode 100644 index 000000000..18bf8aea4 --- /dev/null +++ b/io-hotmoka-nodes-api/src/main/java/io/hotmoka/nodes/api/ConsensusConfig.java @@ -0,0 +1,193 @@ +/* +Copyright 2023 Fausto Spoto + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.hotmoka.nodes.api; + +import java.math.BigInteger; + +import io.hotmoka.annotations.Immutable; + +/** + * A specification of the consensus parameters of a Hotmoka node. This information + * is typically contained in the manifest of the node. + */ +@Immutable +public interface ConsensusConfig { + + /** + * Yields the genesis time, UTC, in ISO8601 pattern. + * + * #return the genesis time + */ + String getGenesisTime(); + + /** + * Yields the chain identifier of the node. + */ + String getChainId(); + + /** + * Yields the maximal length of the error message kept in the store of the node. + * Beyond this threshold, the message gets truncated. + */ + int getMaxErrorLength(); + + /** + * Yields the maximal number of dependencies in the classpath of a transaction. + */ + int getMaxDependencies(); + + /** + * Yields the maximal cumulative size (in bytes) of the instrumented jars of the dependencies + * of a transaction. + */ + long getMaxCumulativeSizeOfDependencies(); + + /** + * Yields true if and only if the use of the {@code @@SelfCharged} annotation is allowed. + */ + boolean allowsSelfCharged(); + + /** + * Yields true if and only if the use of the faucet of the gamete is allowed without a valid signature. + */ + boolean allowsUnsignedFaucet(); + + /** + * Yields true if and only if the gamete of the node can call, for free, the add method of the accounts ledger + * and the mint/burn methods of the accounts, without paying gas and without paying for the minted coins. + */ + boolean allowsMintBurnFromGamete(); + + /** + * Yields true if and only if the static verification of the classes of the jars installed in the node must be skipped. + */ + boolean skipsVerification(); + + /** + * Yields the Base64-encoded public key of the gamete account. + */ + String getPublicKeyOfGamete(); + + /** + * Yields the initial gas price. + */ + BigInteger getInitialGasPrice(); + + /** + * Yields the maximal amount of gas that a non-view transaction can consume. + */ + BigInteger getMaxGasPerTransaction(); + + /** + * Yields true if and only if the node ignores the minimum gas price. + * Hence requests that specify a lower gas price + * than the current gas price of the node are executed anyway. + * This is mainly useful for testing. + */ + boolean ignoresGasPrice(); + + /** + * Yields the units of gas that are aimed to be rewarded at each reward. + * If the actual reward is smaller, the price of gas must decrease. + * If it is larger, the price of gas must increase. + */ + BigInteger getTargetGasAtReward(); + + /** + * Yields how quick the gas consumed at previous rewards is forgotten: + * 0 means never, 1_000_000 means immediately. + * Hence a smaller level means that the latest rewards are heavier + * in the determination of the gas price. + * A value of 0 means that the gas price is constant. + */ + long getOblivion(); + + /** + * Yields the initial inflation applied to the gas consumed by transactions before it gets sent + * as reward to the validators. 1,000,000 means 1%. + * Inflation can be negative. For instance, -300,000 means -0.3%. + */ + long getInitialInflation(); + + /** + * Yields the version of the verification module to use. + */ + int getVerificationVersion(); + + /** + * Yields the initial supply of coins in the node. + */ + BigInteger getInitialSupply(); + + /** + * Yields the final supply of coins in the node. Once the current supply reaches + * this final amount, it remains constant. + */ + BigInteger getFinalSupply(); + + /** + * Yields the initial supply of red coins in the node. + */ + BigInteger getInitialRedSupply(); + + /** + * Yields the amount of coin to pay to start a new poll amount the validators, + * for instance in order to change a consensus parameter. + */ + BigInteger getTicketForNewPoll(); + + /** + * Yields the name of the signature algorithm for signing requests. + */ + String getSignature(); + + /** + * Yields the amount of validators' rewards that gets staked. The rest is sent to the validators immediately. + * 1000000 = 1%. + */ + int getPercentStaked(); + + /** + * Yields extra tax paid when a validator acquires the shares of another validator + * (in percent of the offer cost). 1000000 = 1%. + */ + int getBuyerSurcharge(); + + /** + * Yields the percent of stake that gets slashed for each misbehaving validator. 1000000 means 1%. + */ + int getSlashingForMisbehaving(); + + /** + * Yields the percent of stake that gets slashed for validators that do not behave + * (or do not vote). 1000000 means 1%. + */ + int getSlashingForNotBehaving(); + + /** + * Yields a toml representation of this configuration. + * + * @return the toml representation, as a string + */ + String toToml(); + + @Override + boolean equals(Object other); + + @Override + String toString(); +} \ No newline at end of file diff --git a/io-hotmoka-nodes/pom.xml b/io-hotmoka-nodes/pom.xml index bfa01aa64..11739bfd4 100644 --- a/io-hotmoka-nodes/pom.xml +++ b/io-hotmoka-nodes/pom.xml @@ -29,6 +29,11 @@ io.hotmoka io-hotmoka-annotations ${hotmoka.version} + + + io.hotmoka + toml4j + 0.7.3 io.hotmoka diff --git a/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/ConsensusParams.java b/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/ConsensusParams.java index b074c75d4..ab15e2363 100644 --- a/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/ConsensusParams.java +++ b/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/ConsensusParams.java @@ -281,7 +281,7 @@ public static class Builder { private int slashingForNotBehaving = 500_000; public Builder() { - TimeZone tz = TimeZone.getTimeZone("UTC"); + var tz = TimeZone.getTimeZone("UTC"); DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'"); // Quoted "Z" to indicate UTC, no timezone offset df.setTimeZone(tz); // by default, the genesis time is the time of creation of this object diff --git a/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/internal/ConsensusConfigImpl.java b/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/internal/ConsensusConfigImpl.java new file mode 100644 index 000000000..4c3ea8f11 --- /dev/null +++ b/io-hotmoka-nodes/src/main/java/io/hotmoka/nodes/internal/ConsensusConfigImpl.java @@ -0,0 +1,1016 @@ +/* +Copyright 2023 Fausto Spoto + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.hotmoka.nodes.internal; + +import java.io.FileNotFoundException; +import java.math.BigInteger; +import java.nio.file.Path; + +import com.moandjiezana.toml.Toml; + +import io.hotmoka.annotations.Immutable; +import io.hotmoka.nodes.api.ConsensusConfig; + +/** + * Implementation of the consensus parameters of a Hotmoka node. This information + * is typically contained in the manifest of the node. + */ +@Immutable +public class ConsensusConfigImpl implements ConsensusConfig { + + /** + * The genesis time, UTC, in ISO8601 pattern. + */ + public final String genesisTime; + + /** + * The chain identifier of the node. + */ + public final String chainId; + + /** + * The maximal length of the error message kept in the store of the node. + * Beyond this threshold, the message gets truncated. + */ + public final int maxErrorLength; + + /** + * The maximal number of dependencies in the classpath of a transaction. + */ + public final int maxDependencies; + + /** + * The maximal cumulative size (in bytes) of the instrumented jars of the dependencies + * of a transaction. + */ + public final long maxCumulativeSizeOfDependencies; + + /** + * True if and only if the use of the {@code @@SelfCharged} annotation is allowed. + */ + public final boolean allowsSelfCharged; + + /** + * True if and only if the use of the faucet of the gamete is allowed without a valid signature. + * It defaults to false. + */ + public final boolean allowsUnsignedFaucet; + + /** + * True if and only if the gamete of the node can call, for free, the add method of the accounts ledger + * and the mint/burn methods of the accounts, without paying gas and without paying for the minted coins. + */ + public final boolean allowsMintBurnFromGamete; + + /** + * True if and only if the static verification of the classes of the jars installed in the node must be skipped. + * It defaults to false. + */ + public final boolean skipsVerification; + + /** + * The Base64-encoded public key of the gamete account. + */ + public final String publicKeyOfGamete; + + /** + * The initial gas price. It defaults to 100. + */ + public final BigInteger initialGasPrice; + + /** + * The maximal amount of gas that a non-view transaction can consume. + * It defaults to 1_000_000_000. + */ + public final BigInteger maxGasPerTransaction; + + /** + * True if and only if the node ignores the minimum gas price. + * Hence requests that specify a lower gas price + * than the current gas price of the node are executed anyway. + * This is mainly useful for testing. It defaults to false. + */ + public final boolean ignoresGasPrice; + + /** + * The units of gas that are aimed to be rewarded at each reward. + * If the actual reward is smaller, the price of gas must decrease. + * If it is larger, the price of gas must increase. + * This defaults to 1_000_000. + */ + public final BigInteger targetGasAtReward; + + /** + * How quick the gas consumed at previous rewards is forgotten: + * 0 means never, 1_000_000 means immediately. + * Hence a smaller level means that the latest rewards are heavier + * in the determination of the gas price. + * A value of 0 means that the gas price is constant. + * It defaults to 250_000L. + */ + public final long oblivion; + + /** + * The initial inflation applied to the gas consumed by transactions before it gets sent + * as reward to the validators. 1,000,000 means 1%. + * Inflation can be negative. For instance, -300,000 means -0.3%. + * This defaults to 10,000 (that is, inflation is 0.1% by default). + */ + public final long initialInflation; + + /** + * The version of the verification module to use. It defaults to 0. + */ + public final int verificationVersion; + + /** + * The initial supply of coins in the node. + */ + public final BigInteger initialSupply; + + /** + * The final supply of coins in the node. Once the current supply reaches + * this final amount, it remains constant. + */ + public final BigInteger finalSupply; + + /** + * The initial supply of red coins in the node. + */ + public final BigInteger initialRedSupply; + + /** + * The amount of coin to pay to start a new poll amount the validators, + * for instance in order to change a consensus parameter. + */ + public final BigInteger ticketForNewPoll; + + /** + * The name of the signature algorithm for signing requests. It defaults to "ed25519". + */ + public final String signature; + + /** + * The amount of validators' rewards that gets staked. The rest is sent to the validators immediately. + * 1000000 = 1%. It defaults to 75%. + */ + public final int percentStaked; + + /** + * Extra tax paid when a validator acquires the shares of another validator + * (in percent of the offer cost). 1000000 = 1%. It defaults to 50%. + */ + public final int buyerSurcharge; + + /** + * The percent of stake that gets slashed for each misbehaving validator. 1000000 means 1%. + * It defaults to 1%. + */ + public final int slashingForMisbehaving; + + /** + * The percent of stake that gets slashed for validators that do not behave + * (or do not vote). 1000000 means 1%. It defaults to 0.5%. + */ + public final int slashingForNotBehaving; + + /** + * Full constructor for the builder pattern. + * + * @param builder the builder where information is extracted from + */ + public ConsensusConfigImpl(AbstractBuilder builder) { + this.genesisTime = builder.genesisTime; + this.chainId = builder.chainId; + this.maxErrorLength = builder.maxErrorLength; + this.maxDependencies = builder.maxDependencies; + this.maxCumulativeSizeOfDependencies = builder.maxCumulativeSizeOfDependencies; + this.allowsSelfCharged = builder.allowsSelfCharged; + this.allowsUnsignedFaucet = builder.allowsUnsignedFaucet; + this.allowsMintBurnFromGamete = builder.allowsMintBurnFromGamete; + this.initialGasPrice = builder.initialGasPrice; + this.maxGasPerTransaction = builder.maxGasPerTransaction; + this.ignoresGasPrice = builder.ignoresGasPrice; + this.skipsVerification = builder.skipsVerification; + this.targetGasAtReward = builder.targetGasAtReward; + this.oblivion = builder.oblivion; + this.initialInflation = builder.initialInflation; + this.verificationVersion = builder.verificationVersion; + this.ticketForNewPoll = builder.ticketForNewPoll; + this.initialSupply = builder.initialSupply; + this.finalSupply = builder.finalSupply; + this.initialRedSupply = builder.initialRedSupply; + this.publicKeyOfGamete = builder.publicKeyOfGamete; + this.signature = builder.signature; + this.percentStaked = builder.percentStaked; + this.buyerSurcharge = builder.buyerSurcharge; + this.slashingForMisbehaving = builder.slashingForMisbehaving; + this.slashingForNotBehaving = builder.slashingForNotBehaving; + } + + @Override + public boolean equals(Object other) { + if (other != null && getClass() == other.getClass()) { + var otherConfig = (ConsensusConfigImpl) other; + return genesisTime.equals(otherConfig.genesisTime) && + chainId.equals(otherConfig.chainId) && + maxErrorLength == otherConfig.maxErrorLength && + maxDependencies == otherConfig.maxDependencies && + maxCumulativeSizeOfDependencies == otherConfig.maxCumulativeSizeOfDependencies && + allowsSelfCharged == otherConfig.allowsSelfCharged && + allowsUnsignedFaucet == otherConfig.allowsUnsignedFaucet && + allowsMintBurnFromGamete == otherConfig.allowsMintBurnFromGamete && + initialGasPrice.equals(otherConfig.initialGasPrice) && + maxGasPerTransaction.equals(otherConfig.maxGasPerTransaction) && + ignoresGasPrice == otherConfig.ignoresGasPrice && + skipsVerification == otherConfig.skipsVerification && + targetGasAtReward.equals(otherConfig.targetGasAtReward) && + oblivion == otherConfig.oblivion && + initialInflation == otherConfig.initialInflation && + verificationVersion == otherConfig.verificationVersion && + ticketForNewPoll.equals(otherConfig.ticketForNewPoll) && + initialSupply.equals(otherConfig.initialSupply) && + finalSupply.equals(otherConfig.finalSupply) && + initialRedSupply.equals(otherConfig.initialRedSupply) && + publicKeyOfGamete.equals(otherConfig.publicKeyOfGamete) && + signature.equals(otherConfig.signature) && + percentStaked == otherConfig.percentStaked && + buyerSurcharge == otherConfig.buyerSurcharge && + slashingForMisbehaving == otherConfig.slashingForMisbehaving && + slashingForNotBehaving == otherConfig.slashingForNotBehaving; + } + else + return false; + } + + @Override + public String toString() { + return toToml(); + } + + @Override + public String toToml() { + StringBuilder sb = new StringBuilder(); + sb.append("# This is a TOML config file for Hotmoka nodes.\n"); + sb.append("# For more information about TOML, see https://github.com/toml-lang/toml\n"); + sb.append("# For more information about Hotmoka, see https://www.hotmoka.io\n"); + sb.append("\n"); + sb.append("## Consensus parameters\n"); + sb.append("\n"); + sb.append("# the genesis time, UTC, in ISO8601 pattern\n"); + sb.append("genesis_time = \"" + genesisTime + "\"\n"); + sb.append("\n"); + sb.append("# the chain identifier of the node\n"); + sb.append("chain_id = \"" + chainId + "\"\n"); + sb.append("\n"); + sb.append("# the maximal length of the error message kept in the store of the node\n"); + sb.append("max_error_length = " + maxErrorLength + "\n"); + sb.append("\n"); + sb.append("# the maximal number of dependencies in the classpath of a transaction\n"); + sb.append("max_dependencies = " + maxDependencies + "\n"); + sb.append("\n"); + sb.append("# the maximal cumulative size (in bytes) of the instrumented jars\n"); + sb.append("# of the dependencies of a transaction\n"); + sb.append("max_cumulative_size_of_dependencies = " + maxCumulativeSizeOfDependencies + "\n"); + sb.append("\n"); + sb.append("# true if and only if the use of the @SelfCharged annotation is allowed\n"); + sb.append("allows_self_charged = " + allowsSelfCharged + "\n"); + sb.append("\n"); + sb.append("# true if and only if the use of the faucet of the gamete is allowed without a valid signature\n"); + sb.append("allows_unsigned_faucet = " + allowsUnsignedFaucet + "\n"); + sb.append("\n"); + sb.append("# true if and only if the gamete of the node can call, for free, the add method of the accounts ledger\n"); + sb.append("# and the mint/burn methods of the accounts, without paying gas and without paying for the minted coins\n"); + sb.append("allows_mint_burn_from_gamete = " + allowsMintBurnFromGamete + "\n"); + sb.append("\n"); + sb.append("# true if and only if the static verification of the classes of the jars installed in the node must be skipped\n"); + sb.append("skips_verification = " + skipsVerification + "\n"); + sb.append("\n"); + sb.append("# the Base64-encoded public key of the gamete account\n"); + sb.append("public_key_of_gamete = \"" + publicKeyOfGamete + "\"\n"); + sb.append("\n"); + sb.append("# the initial gas price\n"); + sb.append("initial_gas_price = \"" + initialGasPrice + "\"\n"); + sb.append("\n"); + sb.append("# the maximal amount of gas that a non-view transaction can consume\n"); + sb.append("max_gas_per_transaction = \"" + maxGasPerTransaction + "\"\n"); + sb.append("\n"); + sb.append("# true if and only if the node ignores the minimum gas price;\n"); + sb.append("# hence requests that specify a lower gas price than the current gas price of the node are executed anyway;\n"); + sb.append("# this is mainly useful for testing\n"); + sb.append("ignores_gas_price = " + ignoresGasPrice + "\n"); + sb.append("\n"); + sb.append("# the units of gas that are aimed to be rewarded at each reward;\n"); + sb.append("# if the actual reward is smaller, the price of gas must decrease;\n"); + sb.append("# if it is larger, the price of gas must increase\n"); + sb.append("target_gas_at_reward = \"" + targetGasAtReward + "\"\n"); + + return sb.toString(); + } + + @Override + public String getGenesisTime() { + return genesisTime; + } + + @Override + public String getChainId() { + return chainId; + } + + @Override + public int getMaxErrorLength() { + return maxErrorLength; + } + + @Override + public int getMaxDependencies() { + return maxDependencies; + } + + @Override + public long getMaxCumulativeSizeOfDependencies() { + return maxCumulativeSizeOfDependencies; + } + + @Override + public boolean allowsSelfCharged() { + return allowsSelfCharged; + } + + @Override + public boolean allowsUnsignedFaucet() { + return allowsUnsignedFaucet; + } + + @Override + public boolean allowsMintBurnFromGamete() { + return allowsMintBurnFromGamete; + } + + @Override + public boolean skipsVerification() { + return skipsVerification; + } + + @Override + public String getPublicKeyOfGamete() { + return publicKeyOfGamete; + } + + @Override + public BigInteger getInitialGasPrice() { + return initialGasPrice; + } + + @Override + public BigInteger getMaxGasPerTransaction() { + return maxGasPerTransaction; + } + + @Override + public boolean ignoresGasPrice() { + return ignoresGasPrice; + } + + @Override + public BigInteger getTargetGasAtReward() { + return targetGasAtReward; + } + + @Override + public long getOblivion() { + return oblivion; + } + + @Override + public long getInitialInflation() { + return initialInflation; + } + + @Override + public int getVerificationVersion() { + return verificationVersion; + } + + @Override + public BigInteger getInitialSupply() { + return initialSupply; + } + + @Override + public BigInteger getFinalSupply() { + return finalSupply; + } + + @Override + public BigInteger getInitialRedSupply() { + return initialRedSupply; + } + + @Override + public BigInteger getTicketForNewPoll() { + return ticketForNewPoll; + } + + @Override + public String getSignature() { + return signature; + } + + @Override + public int getPercentStaked() { + return percentStaked; + } + + @Override + public int getBuyerSurcharge() { + return buyerSurcharge; + } + + @Override + public int getSlashingForMisbehaving() { + return slashingForMisbehaving; + } + + @Override + public int getSlashingForNotBehaving() { + return slashingForNotBehaving; + } + + /** + * The builder of a configuration object. + * + * @param the concrete type of the builder + */ + public abstract static class AbstractBuilder> { + private String chainId = ""; + private String genesisTime = ""; + private int maxErrorLength = 300; + private boolean allowsSelfCharged = false; + private boolean allowsUnsignedFaucet = false; + private boolean allowsMintBurnFromGamete = false; + private String signature = "ed25519"; + private BigInteger maxGasPerTransaction = BigInteger.valueOf(1_000_000_000L); + private int maxDependencies = 20; + private long maxCumulativeSizeOfDependencies = 10_000_000; + private BigInteger initialGasPrice = BigInteger.valueOf(100L); + private boolean ignoresGasPrice = false; + private boolean skipsVerification = false; + private BigInteger targetGasAtReward = BigInteger.valueOf(1_000_000L); + private long oblivion = 250_000L; + private long initialInflation = 100_000L; // 0.1% + private int verificationVersion = 0; + private BigInteger initialSupply = BigInteger.ZERO; + private BigInteger finalSupply = BigInteger.ZERO; + private BigInteger initialRedSupply = BigInteger.ZERO; + private String publicKeyOfGamete = ""; + private BigInteger ticketForNewPoll = BigInteger.valueOf(100); + private int percentStaked = 75_000_000; + private int buyerSurcharge = 50_000_000; + private int slashingForMisbehaving = 1_000_000; + private int slashingForNotBehaving = 500_000; + + protected AbstractBuilder() {} + + /** + * Reads the properties of the given TOML file and sets them for + * the corresponding fields of this builder. + * + * @param toml the file + */ + protected AbstractBuilder(Toml toml) { + var genesisTime = toml.getString("genesis_time"); + if (genesisTime != null) + setGenesisTime(genesisTime); + + var chainId = toml.getString("chain_id"); + if (chainId != null) + setChainId(chainId); + + // TODO: remove all type conversions below + var maxErrorLength = toml.getLong("max_error_length"); + if (maxErrorLength != null) + setMaxErrorLength((int) (long) maxErrorLength); + + var maxDependencies = toml.getLong("max_dependencies"); + if (maxDependencies != null) + setMaxDependencies((int) (long) maxDependencies); + + var maxCumulativeSizeOfDependencies = toml.getLong("max_cumulative_size_of_dependencies"); + if (maxCumulativeSizeOfDependencies != null) + setMaxCumulativeSizeOfDependencies(maxCumulativeSizeOfDependencies); + + var allowsSelfCharged = toml.getBoolean("allows_self_charged"); + if (allowsSelfCharged != null) + allowSelfCharged(allowsSelfCharged); + + var allowsUnsignedFaucet = toml.getBoolean("allows_unsigned_faucet"); + if (allowsUnsignedFaucet != null) + allowUnsignedFaucet(allowsUnsignedFaucet); + + var allowsMintBurnFromGamete = toml.getBoolean("allows_mint_burn_from_gamete"); + if (allowsMintBurnFromGamete != null) + allowMintBurnFromGamete(allowsMintBurnFromGamete); + + var signature = toml.getString("signature"); + if (signature != null) + signRequestsWith(signature); + + var maxGasPerTransaction = toml.getString("max_gas_per_transaction"); + if (maxGasPerTransaction != null) + setMaxGasPerTransaction(new BigInteger(maxGasPerTransaction)); + + var initialGasPrice = toml.getString("initial_gas_price"); + if (initialGasPrice != null) + setInitialGasPrice(new BigInteger(initialGasPrice)); + + var ignoresGasPrice = toml.getBoolean("ignores_gas_price"); + if (ignoresGasPrice != null) + ignoreGasPrice(ignoresGasPrice); + + var skipsVerification = toml.getBoolean("skips_verification"); + if (skipsVerification != null) + skipVerification(skipsVerification); + + var targetGasAtReward = toml.getString("target_gas_at_reward"); + if (targetGasAtReward != null) + setTargetGasAtReward(new BigInteger(targetGasAtReward)); + + var oblivion = toml.getLong("oblivion"); + if (oblivion != null) + setOblivion(oblivion); + + var initialInflation = toml.getLong("initial_inflation"); + if (initialInflation != null) + setInitialInflation(initialInflation); + + var verificationVersion = toml.getLong("verification_version"); + if (verificationVersion != null) + setVerificationVersion((int) (long) verificationVersion); + + var initialSupply = toml.getLong("initial_supply"); + if (initialSupply != null) + setInitialSupply(BigInteger.valueOf(initialSupply)); // TODO: maybe should be long? + + var finalSupply = toml.getLong("final_supply"); + if (finalSupply != null) + setFinalSupply(BigInteger.valueOf(finalSupply)); // TODO: maybe should be long? + + var initialRedSupply = toml.getLong("initial_red_supply"); + if (initialRedSupply != null) + setInitialRedSupply(BigInteger.valueOf(initialRedSupply)); // TODO: maybe should be long? + + var ticketForNewPoll = toml.getLong("ticket_for_new_poll"); + if (ticketForNewPoll != null) + setTicketForNewPoll(BigInteger.valueOf(ticketForNewPoll)); // TODO: maybe should be long? + + var publicKeyOfGamete = toml.getString("public_key_of_gamete"); + if (publicKeyOfGamete != null) + setPublicKeyOfGamete(publicKeyOfGamete); + + var percentStaked = toml.getLong("percent_staked"); + if (percentStaked != null) + setPercentStaked((int) (long) percentStaked); + + var buyerSurcharge = toml.getLong("buyer_surcharge"); + if (buyerSurcharge != null) + setBuyerSurcharge((int) (long) buyerSurcharge); + + var slashingForMisbehaving = toml.getLong("slashing_for_misbehaving"); + if (slashingForMisbehaving != null) + setSlashingForMisbehaving((int) (long) slashingForMisbehaving); + + var slashingForNotBehaving = toml.getLong("slashing_for_not_behaving"); + if (slashingForNotBehaving != null) + setSlashingForNotBehaving((int) (long) slashingForNotBehaving); + } + + /** + * Sets the genesis time, UTC, in ISO8601 pattern. + * + * @param genesisTime the genesis time, UTC, in ISO08601 pattern + * @return this builder + */ + public T setGenesisTime(String genesisTime) { + this.genesisTime = genesisTime; + return getThis(); + } + + /** + * Sets the chain identifier of the node. + * + * @param chainId the chain identifier + * @return this builder + */ + public T setChainId(String chainId) { + this.chainId = chainId; + return getThis(); + } + + /** + * Sets the maximal length of the error message kept in the store of the node. + * Beyond this threshold, the message gets truncated. + * + * @param maxErrorLength the maximal length of the error message kept in the store of the node + * @return this builder + */ + public T setMaxErrorLength(int maxErrorLength) { + this.maxErrorLength = maxErrorLength; + return getThis(); + } + + /** + * Sets the maximal number of dependencies in the classpath of a transaction. + * + * @param maxDependencies the maximal number of dependencies in the classpath of a transaction + * @return this builder + */ + public T setMaxDependencies(int maxDependencies) { + this.maxDependencies = maxDependencies; + return getThis(); + } + + /** + * Sets the maximal cumulative size (in bytes) of the instrumented jars of the dependencies + * of a transaction. + * + * @param maxCumulativeSizeOfDependencies the maximal cumulative size (in bytes) of the instrumented + * jars of the dependencies of a transaction + * @return this builder + */ + public T setMaxCumulativeSizeOfDependencies(long maxCumulativeSizeOfDependencies) { + this.maxCumulativeSizeOfDependencies = maxCumulativeSizeOfDependencies; + return getThis(); + } + + /** + * Specifies to allow the {@code @@SelfCharged} annotation in the Takamaka + * code that runs in the node. + * + * @param allowsSelfCharged true if and only if the annotation is allowed + * @return this builder + */ + public T allowSelfCharged(boolean allowsSelfCharged) { + this.allowsSelfCharged = allowsSelfCharged; + return getThis(); + } + + /** + * Specifies to allow the {@code faucet()} methods of the gametes without a valid signature. + * This is only useful for testing networks, where users can freely fill their accounts at the faucet. + * + * @param allowsUnsignedFaucet true if and only if the faucet of the gametes can be used without a valid signature + * @return this builder + */ + public T allowUnsignedFaucet(boolean allowsUnsignedFaucet) { + this.allowsUnsignedFaucet = allowsUnsignedFaucet; + return getThis(); + } + + /** + * Specifies to allow the gamete of the node to call, for free, the add method of the accounts ledger + * and the mint/burn methods of the accounts, without paying gas and without paying for the minted coins. + * + * @param allowsMintBurnFromGamete true if and only if the gamete is allowed + * @return this builder + */ + public T allowMintBurnFromGamete(boolean allowsMintBurnFromGamete) { + this.allowsMintBurnFromGamete = allowsMintBurnFromGamete; + return getThis(); + } + + /** + * Specifies to signature algorithm to use to sign the requests sent to the node. + * It defaults to "ed25519"; + * + * @param signature the name of the signature algorithm. Currently, this includes + * "ed25519", "ed25519det", "sha256dsa", "empty", "qtesla1" and "qtesla3" + * @return this builder + */ + public T signRequestsWith(String signature) { + if (signature == null) + throw new NullPointerException("the signature algorithm name cannot be null"); + + this.signature = signature; + return getThis(); + } + + /** + * Sets the initial gas price. It defaults to 100. + * + * @param initialGasPrice the initial gas price to set. + * @return this builder + */ + public T setInitialGasPrice(BigInteger initialGasPrice) { + if (initialGasPrice == null) + throw new NullPointerException("the initial gas price cannot be null"); + + if (initialGasPrice.signum() <= 0) + throw new IllegalArgumentException("the initial gas price must be positive"); + + this.initialGasPrice = initialGasPrice; + return getThis(); + } + + /** + * Sets the maximal amount of gas that a non-view transaction can consume. + * It defaults to 1_000_000_000. + * + * @return this builder + */ + public T setMaxGasPerTransaction(BigInteger maxGasPerTransaction) { + if (maxGasPerTransaction == null) + throw new NullPointerException("the maximal amount of gas per transaction cannot be null"); + + if (maxGasPerTransaction.signum() <= 0) + throw new IllegalArgumentException("the maximal amount of gas per transaction must be positive"); + + this.maxGasPerTransaction = maxGasPerTransaction; + return getThis(); + } + + /** + * Sets the units of gas that are aimed to be rewarded at each reward. + * If the actual reward is smaller, the price of gas must decrease. + * If it is larger, the price of gas must increase. + * It defaults to 1_000_000. + * + * @return this builder + */ + public T setTargetGasAtReward(BigInteger targetGasAtReward) { + if (targetGasAtReward == null) + throw new NullPointerException("the target gas at reward cannot be null"); + + if (targetGasAtReward.signum() <= 0) + throw new IllegalArgumentException("the target gas at reward must be positive"); + + this.targetGasAtReward = targetGasAtReward; + return getThis(); + } + + /** + * Sets how quick the gas consumed at previous rewards is forgotten: + * 0 means never, 1_000_000 means immediately. + * Hence a smaller level means that the latest rewards are heavier + * in the determination of the gas price. + * Use 0 to keep the gas price constant. + * It defaults to 250_000L. + * + * @return this builder + */ + public T setOblivion(long oblivion) { + if (oblivion < 0 || oblivion > 1_000_000L) + throw new IllegalArgumentException("oblivion must be between 0 and 1_000_000"); + + this.oblivion = oblivion; + return getThis(); + } + + /** + * Sets the initial inflation applied to the gas consumed by transactions before it gets sent + * as reward to the validators. 1,000,000 means 1%. + * Inflation can be negative. For instance, -300,000 means -0.3%. + * It defaults to 100,000 (that is, inflation is 0.1% by default). + * + * @return this builder + */ + public T setInitialInflation(long initialInflation) { + this.initialInflation = initialInflation; + return getThis(); + } + + /** + * Sets the amount of validators' rewards that gets staked. The rest is sent to the validators immediately. + * 1000000 = 1%. It defaults to 75%. + * + * @param percentStaked the buyer surcharge to set + * @return this builder + */ + public T setPercentStaked(int percentStaked) { + if (percentStaked < 0 || percentStaked > 100_000_000L) + throw new IllegalArgumentException("percentStaked must be between 0 and 100_000_000"); + + this.percentStaked = percentStaked; + return getThis(); + } + + /** + * Sets the extra tax paid when a validator acquires the shares of another validator + * (in percent of the offer cost). 1000000 = 1%. It defaults to 50%. + * + * @param buyerSurcharge the buyer surcharge to set + * @return this builder + */ + public T setBuyerSurcharge(int buyerSurcharge) { + if (buyerSurcharge < 0 || buyerSurcharge > 100_000_000L) + throw new IllegalArgumentException("buyerSurcharge must be between 0 and 100_000_000"); + + this.buyerSurcharge = buyerSurcharge; + return getThis(); + } + + /** + * Sets the percent of stake that gets slashed for each misbehaving validator. 1000000 means 1%. + * It defaults to 1%. + * + * @param slashingForMisbehaving the slashing for misbehaving validators + * @return this builder + */ + public T setSlashingForMisbehaving(int slashingForMisbehaving) { + if (slashingForMisbehaving < 0 || slashingForMisbehaving > 100_000_000L) + throw new IllegalArgumentException("slashingForMisbehaving must be between 0 and 100_000_000"); + + this.slashingForMisbehaving = slashingForMisbehaving; + return getThis(); + } + + /** + * Sets the percent of stake that gets slashed for each not behaving (not voting) validator. + * 1000000 means 1%. It defaults to 1%. + * + * @param slashingForNotBehaving the slashing for not behaving validators + * @return this builder + */ + public T setSlashingForNotBehaving(int slashingForNotBehaving) { + if (slashingForNotBehaving < 0 || slashingForNotBehaving > 100_000_000L) + throw new IllegalArgumentException("slashingForNotBehaving must be between 0 and 100_000_000"); + + this.slashingForNotBehaving = slashingForNotBehaving; + return getThis(); + } + + /** + * Specifies that the minimum gas price for transactions is 0, so that the current + * gas price is not relevant for the execution of the transactions. It defaults to false. + * + * @param ignoresGasPrice true if and only if the minimum gas price must be ignored + * @return this builder + */ + public T ignoreGasPrice(boolean ignoresGasPrice) { + this.ignoresGasPrice = ignoresGasPrice; + return getThis(); + } + + /** + * Requires to skip the verification of the classes of the jars installed in the node. + * It defaults to false. + * + * @param skipsVerification true if and only if the verification must be disabled + * @return this builder + */ + public T skipVerification(boolean skipsVerification) { + this.skipsVerification = skipsVerification; + return getThis(); + } + + /** + * Sets the version of the verification module to use. + * It defaults to 0. + * + * @param verificationVersion the version of the verification module + * @return this builder + */ + public T setVerificationVersion(int verificationVersion) { + if (verificationVersion < 0) + throw new IllegalArgumentException("the verification version must be non-negative"); + + this.verificationVersion = verificationVersion; + return getThis(); + } + + /** + * Sets the initial supply of coins of the node. + * It defaults to 0. + * + * @param initialSupply the initial supply of coins of the node + * @return this builder + */ + public T setInitialSupply(BigInteger initialSupply) { + if (initialSupply == null) + throw new NullPointerException("the initial supply cannot be null"); + + if (initialSupply.signum() < 0) + throw new IllegalArgumentException("the initial supply must be non-negative"); + + this.initialSupply = initialSupply; + return getThis(); + } + + /** + * Sets the initial supply of red coins of the node. + * It defaults to 0. + * + * @param initialRedSupply the initial supply of red coins of the node + * @return this builder + */ + public T setInitialRedSupply(BigInteger initialRedSupply) { + if (initialRedSupply == null) + throw new NullPointerException("the initial red supply cannot be null"); + + if (initialRedSupply.signum() < 0) + throw new IllegalArgumentException("the initial red supply must be non-negative"); + + this.initialRedSupply = initialRedSupply; + return getThis(); + } + + /** + * Sets the public key for the gamete account. + * It defaults to "" (hence a non-existent key). + * + * @param publicKeyOfGamete the Base64-encoded public key of the gamete account + * @return this builder + */ + public T setPublicKeyOfGamete(String publicKeyOfGamete) { + if (publicKeyOfGamete == null) + throw new NullPointerException("the public key of the gamete cannot be null"); + + this.publicKeyOfGamete = publicKeyOfGamete; + return getThis(); + } + + /** + * Sets the final supply of coins of the node. + * It defaults to 0. + * + * @param finalSupply the final supply of coins of the node + * @return this builder + */ + public T setFinalSupply(BigInteger finalSupply) { + if (finalSupply == null) + throw new NullPointerException("the final supply cannot be null"); + + if (finalSupply.signum() < 0) + throw new IllegalArgumentException("the final supply must be non-negative"); + + this.finalSupply = finalSupply; + return getThis(); + } + + /** + * Sets the amount of coins that must be payed to start a new poll amount + * to validators, for instance to change a consensus parameter. + * It defaults to 100. + */ + public T setTicketForNewPoll(BigInteger ticketForNewPoll) { + if (ticketForNewPoll == null) + throw new NullPointerException("the ticket for a new poll cannot be null"); + + if (ticketForNewPoll.signum() < 0) + throw new IllegalArgumentException("the ticket for new poll must be non-negative"); + + this.ticketForNewPoll = ticketForNewPoll; + return getThis(); + } + + /** + * Builds the configuration. + * + * @return the configuration + */ + public abstract ConsensusConfig build(); + + /** + * Standard design pattern. See http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 + * + * @return this same builder + */ + protected abstract T getThis(); + + /** + * Loads the TOML file at the given path. + * + * @param path the path + * @return the file + * @throws FileNotFoundException if {@code path} cannot be found + */ + protected static Toml readToml(Path path) throws FileNotFoundException { + try { + return new Toml().read(path.toFile()); + } + catch (RuntimeException e) { + // the toml4j library wraps the FileNotFoundException inside a RuntimeException... + Throwable cause = e.getCause(); + if (cause instanceof FileNotFoundException) + throw (FileNotFoundException) cause; + else + throw e; + } + } + } +} \ No newline at end of file diff --git a/io-hotmoka-nodes/src/main/java/module-info.java b/io-hotmoka-nodes/src/main/java/module-info.java index acb457e3b..5208bc935 100644 --- a/io-hotmoka-nodes/src/main/java/module-info.java +++ b/io-hotmoka-nodes/src/main/java/module-info.java @@ -23,5 +23,6 @@ requires transitive io.hotmoka.beans; requires transitive io.hotmoka.crypto; requires io.hotmoka.annotations; + requires toml4j; requires java.logging; } \ No newline at end of file