mirror of https://github.com/hyperledger/besu
Add wrapper classes for config section of genesis file (#208)
* Introduce classes to wrap JSON config instead of accessing it directly in multiple places. * Fix discrepancy in how CliqueProtocolSchedule and CliquePantheonController loaded the block period configuration.
parent
d650f469f3
commit
824b56f141
@ -0,0 +1,36 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
apply plugin: 'java-library' |
||||
|
||||
jar { |
||||
baseName 'pantheon-config' |
||||
manifest { |
||||
attributes('Implementation-Title': baseName, |
||||
'Implementation-Version': project.version) |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation 'com.fasterxml.jackson.core:jackson-databind' |
||||
implementation 'io.vertx:vertx-core' |
||||
implementation 'org.apache.logging.log4j:log4j-api' |
||||
|
||||
runtime 'org.apache.logging.log4j:log4j-core' |
||||
|
||||
testImplementation project(':testutil') |
||||
|
||||
testImplementation 'org.assertj:assertj-core' |
||||
testImplementation 'org.mockito:mockito-core' |
||||
testImplementation 'junit:junit' |
||||
} |
@ -0,0 +1,37 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
|
||||
public class CliqueConfigOptions { |
||||
|
||||
public static final CliqueConfigOptions DEFAULT = new CliqueConfigOptions(new JsonObject()); |
||||
|
||||
private static final long DEFAULT_EPOCH_LENGTH = 30_000; |
||||
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15; |
||||
|
||||
private final JsonObject cliqueConfigRoot; |
||||
|
||||
public CliqueConfigOptions(final JsonObject cliqueConfigRoot) { |
||||
this.cliqueConfigRoot = cliqueConfigRoot; |
||||
} |
||||
|
||||
public long getEpochLength() { |
||||
return cliqueConfigRoot.getLong("epochLength", DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
public int getBlockPeriodSeconds() { |
||||
return cliqueConfigRoot.getInteger("blockPeriodSeconds", DEFAULT_BLOCK_PERIOD_SECONDS); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import java.util.OptionalInt; |
||||
import java.util.OptionalLong; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
|
||||
public class GenesisConfigOptions { |
||||
|
||||
private static final String ETHASH_CONFIG_KEY = "ethash"; |
||||
private static final String IBFT_CONFIG_KEY = "ibft"; |
||||
private static final String CLIQUE_CONFIG_KEY = "clique"; |
||||
private final JsonObject configRoot; |
||||
|
||||
private GenesisConfigOptions(final JsonObject configRoot) { |
||||
this.configRoot = configRoot != null ? configRoot : new JsonObject(); |
||||
} |
||||
|
||||
public static GenesisConfigOptions fromGenesisConfig(final String genesisConfig) { |
||||
return fromGenesisConfig(new JsonObject(genesisConfig)); |
||||
} |
||||
|
||||
public static GenesisConfigOptions fromGenesisConfig(final JsonObject genesisConfig) { |
||||
return new GenesisConfigOptions(genesisConfig.getJsonObject("config")); |
||||
} |
||||
|
||||
public boolean isEthHash() { |
||||
return configRoot.containsKey(ETHASH_CONFIG_KEY); |
||||
} |
||||
|
||||
public boolean isIbft() { |
||||
return configRoot.containsKey(IBFT_CONFIG_KEY); |
||||
} |
||||
|
||||
public boolean isClique() { |
||||
return configRoot.containsKey(CLIQUE_CONFIG_KEY); |
||||
} |
||||
|
||||
public IbftConfigOptions getIbftConfigOptions() { |
||||
return isIbft() |
||||
? new IbftConfigOptions(configRoot.getJsonObject(IBFT_CONFIG_KEY)) |
||||
: IbftConfigOptions.DEFAULT; |
||||
} |
||||
|
||||
public CliqueConfigOptions getCliqueConfigOptions() { |
||||
return isClique() |
||||
? new CliqueConfigOptions(configRoot.getJsonObject(CLIQUE_CONFIG_KEY)) |
||||
: CliqueConfigOptions.DEFAULT; |
||||
} |
||||
|
||||
public OptionalLong getHomesteadBlockNumber() { |
||||
return getOptionalLong("homesteadBlock"); |
||||
} |
||||
|
||||
public OptionalLong getDaoForkBlock() { |
||||
return getOptionalLong("daoForkBlock"); |
||||
} |
||||
|
||||
public OptionalLong getTangerineWhistleBlockNumber() { |
||||
return getOptionalLong("eip150Block"); |
||||
} |
||||
|
||||
public OptionalLong getSpuriousDragonBlockNumber() { |
||||
return getOptionalLong("eip158Block"); |
||||
} |
||||
|
||||
public OptionalLong getByzantiumBlockNumber() { |
||||
return getOptionalLong("byzantiumBlock"); |
||||
} |
||||
|
||||
public OptionalLong getConstantinopleBlockNumber() { |
||||
return getOptionalLong("constantinopleBlock"); |
||||
} |
||||
|
||||
public OptionalInt getChainId() { |
||||
return configRoot.containsKey("chainId") |
||||
? OptionalInt.of(configRoot.getInteger("chainId")) |
||||
: OptionalInt.empty(); |
||||
} |
||||
|
||||
private OptionalLong getOptionalLong(final String key) { |
||||
return configRoot.containsKey(key) |
||||
? OptionalLong.of(configRoot.getLong(key)) |
||||
: OptionalLong.empty(); |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
|
||||
public class IbftConfigOptions { |
||||
|
||||
public static final IbftConfigOptions DEFAULT = new IbftConfigOptions(new JsonObject()); |
||||
|
||||
private static final long DEFAULT_EPOCH_LENGTH = 30_000; |
||||
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1; |
||||
private static final int DEFAULT_ROUND_EXPIRY_MILLISECONDS = 10000; |
||||
|
||||
private final JsonObject ibftConfigRoot; |
||||
|
||||
public IbftConfigOptions(final JsonObject ibftConfigRoot) { |
||||
this.ibftConfigRoot = ibftConfigRoot; |
||||
} |
||||
|
||||
public long getEpochLength() { |
||||
return ibftConfigRoot.getLong("epochLength", DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
public int getBlockPeriodSeconds() { |
||||
return ibftConfigRoot.getInteger("blockPeriodSeconds", DEFAULT_BLOCK_PERIOD_SECONDS); |
||||
} |
||||
|
||||
public int getRequestTimeoutMillis() { |
||||
return ibftConfigRoot.getInteger("requestTimeout", DEFAULT_ROUND_EXPIRY_MILLISECONDS); |
||||
} |
||||
} |
@ -0,0 +1,70 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import static java.util.Collections.emptyMap; |
||||
import static java.util.Collections.singletonMap; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
import org.junit.Test; |
||||
|
||||
public class CliqueConfigOptionsTest { |
||||
|
||||
private static final long EXPECTED_DEFAULT_EPOCH_LENGTH = 30_000; |
||||
private static final int EXPECTED_DEFAULT_BLOCK_PERIOD = 15; |
||||
|
||||
@Test |
||||
public void shouldGetEpochLengthFromConfig() { |
||||
final CliqueConfigOptions config = fromConfigOptions(singletonMap("epochLength", 10_000)); |
||||
assertThat(config.getEpochLength()).isEqualTo(10_000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFallbackToDefaultEpochLength() { |
||||
final CliqueConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getEpochLength()).isEqualTo(EXPECTED_DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDefaultEpochLengthFromDefaultConfig() { |
||||
assertThat(CliqueConfigOptions.DEFAULT.getEpochLength()) |
||||
.isEqualTo(EXPECTED_DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetBlockPeriodFromConfig() { |
||||
final CliqueConfigOptions config = fromConfigOptions(singletonMap("blockPeriodSeconds", 5)); |
||||
assertThat(config.getBlockPeriodSeconds()).isEqualTo(5); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFallbackToDefaultBlockPeriod() { |
||||
final CliqueConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getBlockPeriodSeconds()).isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDefaultBlockPeriodFromDefaultConfig() { |
||||
assertThat(CliqueConfigOptions.DEFAULT.getBlockPeriodSeconds()) |
||||
.isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); |
||||
} |
||||
|
||||
private CliqueConfigOptions fromConfigOptions(final Map<String, Object> cliqueConfigOptions) { |
||||
return GenesisConfigOptions.fromGenesisConfig( |
||||
new JsonObject(singletonMap("config", singletonMap("clique", cliqueConfigOptions)))) |
||||
.getCliqueConfigOptions(); |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import static java.util.Collections.emptyMap; |
||||
import static java.util.Collections.singletonMap; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
import org.junit.Test; |
||||
|
||||
public class GenesisConfigOptionsTest { |
||||
|
||||
@Test |
||||
public void shouldUseEthHashWhenEthHashInConfig() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("ethash", emptyMap())); |
||||
assertThat(config.isEthHash()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotUseEthHashIfEthHashNotPresent() { |
||||
final GenesisConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.isEthHash()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldUseIbftWhenIbftInConfig() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("ibft", emptyMap())); |
||||
assertThat(config.isIbft()).isTrue(); |
||||
assertThat(config.getIbftConfigOptions()).isNotSameAs(IbftConfigOptions.DEFAULT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotUseIbftIfIbftNotPresent() { |
||||
final GenesisConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.isIbft()).isFalse(); |
||||
assertThat(config.getIbftConfigOptions()).isSameAs(IbftConfigOptions.DEFAULT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldUseCliqueWhenCliqueInConfig() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("clique", emptyMap())); |
||||
assertThat(config.isClique()).isTrue(); |
||||
assertThat(config.getCliqueConfigOptions()).isNotSameAs(CliqueConfigOptions.DEFAULT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotUseCliqueIfCliqueNotPresent() { |
||||
final GenesisConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.isClique()).isFalse(); |
||||
assertThat(config.getCliqueConfigOptions()).isSameAs(CliqueConfigOptions.DEFAULT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetHomesteadBlockNumber() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("homesteadBlock", 1000)); |
||||
assertThat(config.getHomesteadBlockNumber()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDaoForkBlockNumber() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("daoForkBlock", 1000)); |
||||
assertThat(config.getDaoForkBlock()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetTangerineWhistleBlockNumber() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("eip150Block", 1000)); |
||||
assertThat(config.getTangerineWhistleBlockNumber()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetSpuriousDragonBlockNumber() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("eip158Block", 1000)); |
||||
assertThat(config.getSpuriousDragonBlockNumber()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetByzantiumBlockNumber() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("byzantiumBlock", 1000)); |
||||
assertThat(config.getByzantiumBlockNumber()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetConstantinopleBlockNumber() { |
||||
final GenesisConfigOptions config = |
||||
fromConfigOptions(singletonMap("constantinopleBlock", 1000)); |
||||
assertThat(config.getConstantinopleBlockNumber()).hasValue(1000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() { |
||||
final GenesisConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getHomesteadBlockNumber()).isEmpty(); |
||||
assertThat(config.getDaoForkBlock()).isEmpty(); |
||||
assertThat(config.getTangerineWhistleBlockNumber()).isEmpty(); |
||||
assertThat(config.getSpuriousDragonBlockNumber()).isEmpty(); |
||||
assertThat(config.getByzantiumBlockNumber()).isEmpty(); |
||||
assertThat(config.getConstantinopleBlockNumber()).isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetChainIdWhenSpecified() { |
||||
final GenesisConfigOptions config = fromConfigOptions(singletonMap("chainId", 32)); |
||||
assertThat(config.getChainId()).hasValue(32); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSupportEmptyGenesisConfig() { |
||||
final GenesisConfigOptions config = GenesisConfigOptions.fromGenesisConfig("{}"); |
||||
assertThat(config.isEthHash()).isFalse(); |
||||
assertThat(config.isIbft()).isFalse(); |
||||
assertThat(config.isClique()).isFalse(); |
||||
assertThat(config.getHomesteadBlockNumber()).isEmpty(); |
||||
} |
||||
|
||||
private GenesisConfigOptions fromConfigOptions(final Map<String, Object> options) { |
||||
return GenesisConfigOptions.fromGenesisConfig( |
||||
new JsonObject(Collections.singletonMap("config", options))); |
||||
} |
||||
} |
@ -0,0 +1,88 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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 tech.pegasys.pantheon.config; |
||||
|
||||
import static java.util.Collections.emptyMap; |
||||
import static java.util.Collections.singletonMap; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
import org.junit.Test; |
||||
|
||||
public class IbftConfigOptionsTest { |
||||
|
||||
private static final int EXPECTED_DEFAULT_EPOCH_LENGTH = 30_000; |
||||
private static final int EXPECTED_DEFAULT_BLOCK_PERIOD = 1; |
||||
private static final int EXPECTED_DEFAULT_REQUEST_TIMEOUT = 10_000; |
||||
|
||||
@Test |
||||
public void shouldGetEpochLengthFromConfig() { |
||||
final IbftConfigOptions config = fromConfigOptions(singletonMap("epochLength", 10_000)); |
||||
assertThat(config.getEpochLength()).isEqualTo(10_000); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFallbackToDefaultEpochLength() { |
||||
final IbftConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getEpochLength()).isEqualTo(EXPECTED_DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDefaultEpochLengthFromDefaultConfig() { |
||||
assertThat(IbftConfigOptions.DEFAULT.getEpochLength()).isEqualTo(EXPECTED_DEFAULT_EPOCH_LENGTH); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetBlockPeriodFromConfig() { |
||||
final IbftConfigOptions config = fromConfigOptions(singletonMap("blockPeriodSeconds", 5)); |
||||
assertThat(config.getBlockPeriodSeconds()).isEqualTo(5); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFallbackToDefaultBlockPeriod() { |
||||
final IbftConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getBlockPeriodSeconds()).isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDefaultBlockPeriodFromDefaultConfig() { |
||||
assertThat(IbftConfigOptions.DEFAULT.getBlockPeriodSeconds()) |
||||
.isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetRequestTimeoutFromConfig() { |
||||
final IbftConfigOptions config = fromConfigOptions(singletonMap("requestTimeout", 5)); |
||||
assertThat(config.getRequestTimeoutMillis()).isEqualTo(5); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldFallbackToDefaultRequestTimeout() { |
||||
final IbftConfigOptions config = fromConfigOptions(emptyMap()); |
||||
assertThat(config.getRequestTimeoutMillis()).isEqualTo(EXPECTED_DEFAULT_REQUEST_TIMEOUT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetDefaultRequestTimeoutFromDefaultConfig() { |
||||
assertThat(IbftConfigOptions.DEFAULT.getRequestTimeoutMillis()) |
||||
.isEqualTo(EXPECTED_DEFAULT_REQUEST_TIMEOUT); |
||||
} |
||||
|
||||
private IbftConfigOptions fromConfigOptions(final Map<String, Object> ibftConfigOptions) { |
||||
return GenesisConfigOptions.fromGenesisConfig( |
||||
new JsonObject(singletonMap("config", singletonMap("ibft", ibftConfigOptions)))) |
||||
.getIbftConfigOptions(); |
||||
} |
||||
} |
Loading…
Reference in new issue