diff --git a/build.gradle b/build.gradle index 415f535d4..44ae98fc8 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ ext { logbackEsAppenderVersion = '1.6' logbackJinoVersion = '3.0.12' equinoxVersion = '3.13.0.v20180226-1711' - esVersion = '6.6.1' + elasticSearchVersion = '7.0.1' } allprojects { @@ -111,7 +111,7 @@ project(':yggdrash-core') { compile "com.google.code.gson:gson:${gsonVersion}" compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" compile "com.typesafe:config:${typesafeVersion}" - compile "org.elasticsearch.client:transport:${esVersion}" + compile "org.elasticsearch.client:transport:${elasticSearchVersion}" compile 'org.beryx:text-io:3.3.0' compileOnly "javax.annotation:javax.annotation-api:${annotationVersion}" testCompile "org.assertj:assertj-core:${assertjVersion}" diff --git a/contracts/stem/build.gradle b/contracts/stem/build.gradle new file mode 100644 index 000000000..c492b3434 --- /dev/null +++ b/contracts/stem/build.gradle @@ -0,0 +1,116 @@ +import java.security.MessageDigest + +ext { + importPackage = + "io.yggdrash.common.contract" + + ",io.yggdrash.common.contract.method" + + ",io.yggdrash.common.contract.vo" + + ",io.yggdrash.common.contract.vo.dpoa" + + ",io.yggdrash.common.contract.vo.dpoa.tx" + + ",io.yggdrash.common.crypto" + + ",io.yggdrash.common.crypto.jce" + + ",io.yggdrash.common.exception" + + ",io.yggdrash.common.store" + + ",io.yggdrash.common.store.datasource" + + ",io.yggdrash.common.utils" + + ",org.osgi.framework" + + ",org.osgi.util.tracker" + + ",com.google.gson" + + ",org.w3c.dom" + + ",org.slf4j" + + ",java.math" + + ",io.yggdrash.contract.core" + + ",io.yggdrash.contract.core.annotation" + + ",io.yggdrash.contract.core.store" + + + excludeList = ["META-INF/LICENSE", "META-INF/NOTICE", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "META-INF/NOTICE"] +} + +// project directory setup +def rootPath = file('.').absolutePath +def buildContract = rootPath + '/build/contract' +def projectContractPath = rootPath + '/../../resources/contract' + + +task contractsPackage(type: Jar) { + //Add current module source + from(sourceSets.main.output) { + include "**" + exclude(["system-contracts"]) + } + + manifest { + attributes( + 'Bundle-ManifestVersion': '2', + 'Bundle-Name': 'StemContract', + 'Bundle-Description': 'Stem Contract', + 'Bundle-Vendor': 'YGGDRASH', + 'Bundle-SymbolicName': 'io.yggdrash.contract.StemContract', + 'Bundle-Version': '1.0.0', + 'Bundle-Activator': 'io.yggdrash.contract.StemContract', + 'Import-Package': "${importPackage}" + ) + } + exclude(excludeList) + preserveFileTimestamps = false + + // [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension] + project.archivesBaseName = manifest.attributes.get('Bundle-Name') + project.version = manifest.attributes.get('Bundle-Version') +} + + + +def generateSha1(File file) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + file.eachByte 4096, {bytes, size -> + md.update(bytes, 0, size); + } + return md.digest().collect {String.format "%02x", it}.join(); +} + +task deleteOutputContract(type: Delete) { + delete buildContract +} + +task copyContractToResrouce(type: Copy) { + dependsOn deleteOutputContract + dependsOn contractsPackage + + file("${buildContract}").mkdirs() + + // Copy Contract File + from file('build/libs') + into file("${buildContract}") + exclude "${project.name}*" + + doLast { + def libFiles = file("${buildContract}").listFiles() + libFiles.each { + def sha1Name = generateSha1(it)+'.jar' + def sha1File = file("${buildContract}/"+sha1Name) + if (it.getName().endsWith('.jar') && it.getName() != sha1Name) { + println it.getName() + ' -> ' + sha1Name + it.renameTo(sha1File) + } + } + } +} +task copyContractToProject(type: Copy) { + dependsOn copyContractToResrouce + + from file("${buildContract}") + into file("${projectContractPath}") +} + +jar { + dependsOn copyContractToProject +} + +dependencies { + compile project(':yggdrash-common') + compile project(':yggdrash-contract-core') + + testCompile project(':yggdrash-core') +} \ No newline at end of file diff --git a/contracts/stem/src/main/java/io/yggdrash/contract/StemContract.java b/contracts/stem/src/main/java/io/yggdrash/contract/StemContract.java new file mode 100644 index 000000000..d31dd8a89 --- /dev/null +++ b/contracts/stem/src/main/java/io/yggdrash/contract/StemContract.java @@ -0,0 +1,578 @@ + +package io.yggdrash.contract; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.yggdrash.common.contract.Contract; +import io.yggdrash.common.contract.standard.CoinStandard; +import io.yggdrash.common.contract.vo.PrefixKeyEnum; +import io.yggdrash.common.crypto.ECKey; +import io.yggdrash.common.crypto.HashUtil; +import io.yggdrash.common.crypto.HexUtil; +import io.yggdrash.common.store.BranchStateStore; +import io.yggdrash.common.utils.BranchUtil; +import io.yggdrash.common.utils.ByteUtil; +import io.yggdrash.common.utils.JsonUtil; +import io.yggdrash.contract.core.ExecuteStatus; +import io.yggdrash.contract.core.TransactionReceipt; +import io.yggdrash.contract.core.annotation.ContractBranchStateStore; +import io.yggdrash.contract.core.annotation.ContractQuery; +import io.yggdrash.contract.core.annotation.ContractStateStore; +import io.yggdrash.contract.core.annotation.ContractTransactionReceipt; +import io.yggdrash.contract.core.annotation.Genesis; +import io.yggdrash.contract.core.annotation.InvokeTransaction; +import io.yggdrash.contract.core.store.ReadWriterStore; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; + +import static io.yggdrash.common.config.Constants.BRANCH_ID; + + +public class StemContract implements BundleActivator, ServiceListener { + private static final Logger log = LoggerFactory.getLogger(StemContract.class); + //private ServiceRegistration registration; + + // Get other Service + private CoinStandard asset; + + @Override + public void start(BundleContext context) { + log.info("Start stem contract"); + + //Find for service in another bundle + Hashtable props = new Hashtable<>(); + props.put("YGGDRASH", "Stem"); + context.registerService(StemService.class.getName(), new StemService(), props); + //Register our service in the bundle context using the name. + //registration = context.registerService(StemService.class.getName(), new StemService(), props); + } + + @Override + public void stop(BundleContext context) { + log.info("Stop stem contract"); + //TODO Why don't unregister the service? + //registration.unregister(); + } + + @Override + public void serviceChanged(ServiceEvent event) { + // get YEED contract in this + // + } + + public void setAsset(CoinStandard coinStandard) { + this.asset = coinStandard; + } + + + public static class StemService implements Contract { + private static final Logger log = LoggerFactory.getLogger(StemContract.class); + + @ContractBranchStateStore + BranchStateStore branchStateStore; + + @ContractStateStore + ReadWriterStore state; + + + @ContractTransactionReceipt + TransactionReceipt txReceipt; + + @Genesis + @InvokeTransaction // TODO remove InvokeTransaction + public TransactionReceipt init(JsonObject param) { + log.info("[StemContract | genesis] SUCCESS! param => {}"); + return txReceipt; + } + + /** + * Returns the id of a registered branch + * + * @param params branch : The branch.json to register on the stem + */ + @InvokeTransaction + public void create(JsonObject params) { + // TODO store branch + // Validate branch spec + // params + + JsonObject branch = params.getAsJsonObject("branch"); + + + // branch verify + if (!branchVerify(branch)) { + this.txReceipt.setStatus(ExecuteStatus.FALSE); + this.txReceipt.addLog("Branch had not verified"); + return; + } + + // calculation branch Id + String branchId = HexUtil.toHexString(BranchUtil.branchIdGenerator(branch)); + log.debug("branchId : {}", branchId); + // prefix Branch_id + + String governanceContractName = branch.get("governanceContract").getAsString(); + + boolean save = saveBranch(branchId, branch); + if (!save) { + this.txReceipt.setStatus(ExecuteStatus.FALSE); + return; + } + + // get Validator + Set branchContracts = getContract(branchId); + JsonObject governanceContract = null; + for (JsonObject contract: branchContracts) { + if (governanceContractName.equals(contract.get("name").getAsString())) { + governanceContract = contract; + break; + } + } + + if (governanceContract == null) { + this.txReceipt.setStatus(ExecuteStatus.FALSE); + this.txReceipt.addLog("Branch has no governanceContract"); + return; + } + + JsonArray validators = governanceContract + .get("init") + .getAsJsonObject() + .get("validators") + .getAsJsonArray(); + + // validators verify + Set validatorSet = new HashSet<>(); + for (JsonElement validator : validators) { + String validatorString = validator.getAsString(); + validatorSet.add(validatorString); + // check validator is address + if (HexUtil.addressStringToBytes(validatorString) == ByteUtil.EMPTY_BYTE_ARRAY) { + this.txReceipt.setStatus(ExecuteStatus.FALSE); + this.txReceipt.addLog(String.format("validator %s is not account", validatorString)); + return; + } + } + + // check validator is unique in list + if (validatorSet.size() != validators.size()) { + this.txReceipt.setStatus(ExecuteStatus.FALSE); + this.txReceipt.addLog(String.format("validator list is unique accounts list")); + return; + } + log.debug(" is validator contain {}",validatorSet.contains(txReceipt.getIssuer())); + log.debug(" is branchStateStore contain {}",branchStateStore.isValidator(txReceipt.getIssuer())); + // Check issuer is validator or yggdrash validator + if (!(validatorSet.contains(txReceipt.getIssuer()) + || branchStateStore.isValidator(txReceipt.getIssuer()))) { + // Check issuer is not yggdrash validator + + this.txReceipt.setStatus(ExecuteStatus.FALSE); + this.txReceipt.addLog("Issuer is not branch validator"); + return; + } + + JsonObject validatorObject = new JsonObject(); + validatorObject.add("validators", validators); + + saveValidators(branchId, validatorObject); + + // get meta information + JsonObject branchCopy = branch.deepCopy(); + + // get branch Meta information + branchCopy.remove("contracts"); + branchCopy.remove("consensus"); + + // save Meta information + saveBranchMeta(branchId, branchCopy); + + // check fee + // check fee govonence + // get validator + // get meta information + + this.txReceipt.setStatus(ExecuteStatus.SUCCESS); + this.txReceipt.addLog(String.format("Branch %s is created", branchId)); + + + } + + /** + * Returns the id of a updated branch + * + * @param params branchId The Id of the branch to update + * branch The branch.json to update on the stem + */ + @InvokeTransaction + public void update(JsonObject params) { + // TODO update branch meta information + + // get branch id + String branchId = params.get("branchId").getAsString(); + + // check branchId Exist + + // check branch validator + if (!checkBranchValidators(branchId)) { + // Transaction Issuer is not validator + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("Issuer is not branch validator"); + return; + } + + JsonObject updateBranch = params.get("branch").getAsJsonObject(); + JsonObject originBranchMeta = getBranchMeta(branchId); + + if (originBranchMeta == null) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("branch is not exist"); + } else { + originBranchMeta = metaMerge(originBranchMeta, updateBranch); + // save branch meta information + saveBranchMeta(branchId, originBranchMeta); + txReceipt.setStatus(ExecuteStatus.SUCCESS); + } + } + + @InvokeTransaction + public void transferBranch(JsonObject params) { + // TODO transfer Yeed to Branch + String branchId = params.get(BRANCH_ID).getAsString(); + } + + @InvokeTransaction + public void withdrawBranch(JsonObject params) { + // TODO withdraw Yeed from Branch + + String branchId = params.get(BRANCH_ID).getAsString(); + // check branch validator + if (!checkBranchValidators(branchId)) { + // Transaction Issuer is not validator + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("Issuer is not branch validator"); + return; + } + + } + + + @InvokeTransaction + public void updateValidator(JsonObject params) { + // TODO update branch meta information + + // get branch id + String branchId = params.get("branchId").getAsString(); + Long blockHeight = params.get("blockHeight").getAsLong(); + String proposer = params.get("proposer").getAsString(); + String targetValidator = params.get("targetValidator").getAsString(); + StemOperation operatingFlag = StemOperation.fromValue(params.get("operatingFlag").getAsString()); + + // Get Validator Set + JsonObject validators = getValidators(branchId); + + if (validators == null) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("validator is not exist"); + return; + } + JsonArray validatorArray = validators.get("validators").getAsJsonArray(); + Set validatorSet = JsonUtil.convertJsonArrayToSet(validatorArray); + // check is branch validator + if (!validatorSet.contains(txReceipt.getIssuer())) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("issuer is not validator"); + return; + } + + byte[] message = ByteUtil.merge( + HexUtil.hexStringToBytes(branchId), + ByteUtil.longToBytes(blockHeight), + HexUtil.hexStringToBytes(proposer), + HexUtil.hexStringToBytes(targetValidator), + operatingFlag.toValue().getBytes(StandardCharsets.UTF_8) + ); + JsonArray signed = params.get("signed").getAsJsonArray(); + + // all message is sha3hashed + message = HashUtil.sha3(message); + + int voteCount = (int)Math.ceil(1.0*validatorSet.size()*2/3); + int vote = 0; + log.debug("vote count {}", voteCount); + + // verify signed + List signedList = JsonUtil.convertJsonArrayToStringList(signed); + // for check validator set + Set checkValidator = new HashSet<>(); + checkValidator.addAll(validatorSet); + + // check branch validators vote[] + for (String sign : signedList) { + // TODO move signature to contract core + byte[] signatureArray = HexUtil.hexStringToBytes(sign); + ECKey.ECDSASignature signature = new ECKey.ECDSASignature(signatureArray); + int realV = signatureArray[0] - 27; + byte[] address = ECKey.recoverAddressFromSignature(realV, signature, message); + String addressHexString = HexUtil.toHexString(address); + // check validator set + if (checkValidator.contains(addressHexString)) { + vote++; + checkValidator.remove(addressHexString); + } + } + checkValidator.clear(); + + if (vote < voteCount) { + // 정족수 부족 + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("Lack of quorum"); + return; + } + + + if (operatingFlag == StemOperation.ADD_VALIDATOR) { + // add Validator + // check validator is exist + if (validatorSet.contains(targetValidator)) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("Target validator is exist in validator set"); + return; + } + // add Validator + validatorArray.add(targetValidator); + JsonObject validatorList = new JsonObject(); + validatorList.add("validators", validatorArray); + saveValidators(branchId, validatorList); + txReceipt.setStatus(ExecuteStatus.SUCCESS); + txReceipt.addLog("new validator add in branch"); + return; + + } else if(operatingFlag == StemOperation.REMOVE_VALIDATOR) { + // remove validator + if (!validatorSet.contains(targetValidator)) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("Target validator is not exist in validator set"); + return; + } + validatorSet.remove(targetValidator); + validatorArray = JsonUtil.convertCollectionToJsonArray(validatorSet); + JsonObject validatorList = new JsonObject(); + validatorList.add("validators", validatorArray); + saveValidators(branchId, validatorList); + txReceipt.setStatus(ExecuteStatus.SUCCESS); + txReceipt.addLog("validator remove in branch"); + return; + + } else if(operatingFlag == StemOperation.REPLACE_VALIDATOR) { + // replace validator + // param get validator list + // remove proposer + // add targetValidator + // check proposer exist and targetValidator not exist + if (!validatorSet.contains(proposer) || validatorSet.contains(targetValidator)) { + // propser + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("proposer not exist validator set or new validator exist validator set"); + return; + } + // remove and add + validatorSet.remove(proposer); + validatorSet.add(targetValidator); + + // save validator set + JsonObject validatorList = new JsonObject(); + validatorArray = JsonUtil.convertCollectionToJsonArray(validatorSet); + validatorList.add("validators", validatorArray); + saveValidators(branchId, validatorList); + txReceipt.setStatus(ExecuteStatus.SUCCESS); + txReceipt.addLog(String.format("validator replace %s to %s", proposer, targetValidator)); + return; + + } else if(operatingFlag == StemOperation.UPDATE_VALIDATOR_SET) { + // update all validator set + JsonObject newValidatorSet = new JsonObject(); + JsonArray validatorList = params.get("validators").getAsJsonArray(); + newValidatorSet.add("validators", validatorList); + + // target validator is list hash(sha3ommit12) + byte[] validatorsByteArray = newValidatorSet.toString().getBytes(StandardCharsets.UTF_8); + byte[] validatorsSha3 = HashUtil.sha3omit12(validatorsByteArray); + String calculateTargetValidator = HexUtil.toHexString(validatorsSha3); + + // check and verify validatorList + if (!targetValidator.equals(calculateTargetValidator)) { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("target Validator set is invalid"); + return; + } + + // update Validator set + saveValidators(branchId, newValidatorSet); + + txReceipt.setStatus(ExecuteStatus.SUCCESS); + txReceipt.addLog("validator set change"); + return; + + + } + + + // save branch validator set information + + } + + private boolean saveBranch(String branchId, JsonObject branch) { + String branchIdKey = String.format("%s%s", PrefixKeyEnum.STEM_BRANCH, branchId); + + if (!this.state.contains(branchIdKey)) { + this.state.put(branchIdKey, branch); + return true; + } + return false; + } + + /** + * @param params branch id + * + * @return branch json object + */ + @ContractQuery + public JsonObject getBranch(JsonObject params) { + // TODO get branch information + String branchId = params.get(BRANCH_ID).getAsString(); + JsonObject branch = getBranch(branchId); + + // TODO fee not enough mesaage + return branch; + } + + public JsonObject getBranch(String branchId) { + String branchIdKey = String.format("%s%s", PrefixKeyEnum.STEM_BRANCH, branchId); + return this.state.get(branchIdKey); + } + + private void saveBranchMeta(String branchId, JsonObject branchMeta) { + String branchMetaKey = String.format("%s%s", PrefixKeyEnum.STEM_META, branchId); + this.state.put(branchMetaKey, branchMeta); + } + + @ContractQuery + public JsonObject getBranchMeta(JsonObject param) { + String branchId = param.get(BRANCH_ID).getAsString(); + + return getBranchMeta(branchId); + } + + public JsonObject getBranchMeta(String branchId) { + String branchMetaKey = String.format("%s%s", PrefixKeyEnum.STEM_META, branchId); + return this.state.get(branchMetaKey); + } + + private void saveValidators(String branchId, JsonObject validators) { + String branchValidatorKey = String.format("%s%s", PrefixKeyEnum.STEM_BRANCH_VALIDATOR, branchId); + this.state.put(branchValidatorKey, validators); + } + + public JsonObject getValidators(String branchId) { + String branchValidatorKey = String.format("%s%s", PrefixKeyEnum.STEM_BRANCH_VALIDATOR, branchId); + return this.state.get(branchValidatorKey); + } + + + public JsonObject metaMerge(JsonObject branchMeta, JsonObject branchMetaUpdate) { + // update 가능 항목 + List keyList = Arrays.asList(new String[]{"description"}); + keyList.stream().forEach(key -> { + if (branchMeta.has(key) && branchMetaUpdate.has(key)) { + branchMeta.addProperty(key, branchMetaUpdate.get(key).getAsString()); + } + }); + + return branchMeta; + } + + /** + * Returns boolean + * + * @param branchId + * */ + public void messageCall(String branchId) { + // TODO message call to contract + // TODO isEnoughFee + } + + /** + * @param params branch id + * + * @return contract json object + */ + @ContractQuery + public Set getContract(JsonObject params) { + String branchId = params.get(BRANCH_ID).getAsString(); + return getContract(branchId); + } + + public Set getContract(String branchId) { + Set contractSet = new HashSet<>(); + + JsonObject branch = getBranch(branchId); + if (branch != null) { + JsonArray contracts = branch.get("contracts").getAsJsonArray(); + for (JsonElement c : contracts) { + contractSet.add(c.getAsJsonObject()); + } + } + return contractSet; + } + + /** + * @param params branch id + * + * @return fee state + */ + @ContractQuery + public BigInteger feeState(JsonObject params) { + String branchId = params.get(BRANCH_ID).getAsString(); + // TODO get branch feeState and calculate + return BigInteger.ZERO; + + } + + private boolean branchVerify(JsonObject branch) { + // check property + boolean verify = true; + List existProperty = Arrays.asList( + new String[]{"name", "symbol", "property", "contracts", "governanceContract"}); + + for (String perpertyKey :existProperty) { + verify &= branch.get(perpertyKey).isJsonNull() != true; + } + + return verify; + } + + private boolean checkBranchValidators(String branchId) { + // check branch validator + JsonObject validators = getValidators(branchId); + // check issuer + if (validators == null || !validators.toString().contains(txReceipt.getIssuer())) { + return false; + } + return true; + } + + } +} \ No newline at end of file diff --git a/contracts/stem/src/main/java/io/yggdrash/contract/StemOperation.java b/contracts/stem/src/main/java/io/yggdrash/contract/StemOperation.java new file mode 100644 index 000000000..1cb60c331 --- /dev/null +++ b/contracts/stem/src/main/java/io/yggdrash/contract/StemOperation.java @@ -0,0 +1,32 @@ +package io.yggdrash.contract; + +import java.util.Arrays; + +public enum StemOperation { + ADD_VALIDATOR("1"), + REMOVE_VALIDATOR("2"), + REPLACE_VALIDATOR("3"), + UPDATE_VALIDATOR_SET("4") + ; + + private final String flag; + + StemOperation(String flag) { + this.flag = flag; + } + + public String toValue() { + return this.flag; + } + + public static StemOperation fromValue(String flag) { + StemOperation[] values = StemOperation.values(); + for(StemOperation value : values) { + if (value.flag.equals(flag) ) { + return value; + } + } + return null; + + } +} diff --git a/contracts/stem/src/test/java/io/yggdrash/contract/StemContractTest.java b/contracts/stem/src/test/java/io/yggdrash/contract/StemContractTest.java new file mode 100644 index 000000000..52ae47f4d --- /dev/null +++ b/contracts/stem/src/test/java/io/yggdrash/contract/StemContractTest.java @@ -0,0 +1,286 @@ +/* + * Copyright 2018 Akashic Foundation + * + * 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.yggdrash.contract; + +import com.google.gson.JsonObject; +import io.yggdrash.common.contract.vo.PrefixKeyEnum; +import io.yggdrash.common.contract.vo.dpoa.Validator; +import io.yggdrash.common.crypto.HexUtil; +import io.yggdrash.common.store.StateStore; +import io.yggdrash.common.store.datasource.HashMapDbSource; +import io.yggdrash.common.utils.BranchUtil; +import io.yggdrash.common.utils.ContractUtils; +import io.yggdrash.common.utils.FileUtil; +import io.yggdrash.common.utils.JsonUtil; +import io.yggdrash.contract.core.ExecuteStatus; +import io.yggdrash.contract.core.TransactionReceipt; +import io.yggdrash.contract.core.TransactionReceiptImpl; +import io.yggdrash.contract.core.annotation.ContractBranchStateStore; +import io.yggdrash.contract.core.annotation.ContractStateStore; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class StemContractTest { + private static final Logger log = LoggerFactory.getLogger(StemContractTest.class); + private static final StemContract.StemService stemContract = new StemContract.StemService(); + + private static final File branchFile = new File("../../yggdrash-core/src/main/resources/branch-yggdrash.json"); + + private Field txReceiptField; + TestYeed testYeed = new TestYeed(); + + JsonObject branchSample; + String branchId; + StateStore stateStore; + + @Before + public void setUp() throws IllegalAccessException, IOException { + // Steup StemContract + stateStore = new StateStore(new HashMapDbSource()); + + List txReceipt = ContractUtils.txReceiptFields(stemContract); + if (txReceipt.size() == 1) { + txReceiptField = txReceipt.get(0); + } + for (Field f : ContractUtils.contractFields(stemContract, ContractStateStore.class)) { + f.setAccessible(true); + f.set(stemContract, stateStore); + } + // 1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e + JsonObject obj = new JsonObject(); + obj.addProperty("address", "1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e"); + assertTrue("setup", BigInteger.ZERO.compareTo(testYeed.balanceOf(obj)) < 0); + + TestBranchStateStore branchStateStore = new TestBranchStateStore(); + branchStateStore.getValidators().getValidatorMap() + .put("81b7e08f65bdf5648606c89998a9cc8164397647", + new Validator("81b7e08f65bdf5648606c89998a9cc8164397647")); + + for (Field f : ContractUtils.contractFields(stemContract, ContractBranchStateStore.class)) { + f.setAccessible(true); + f.set(stemContract, branchStateStore); + } + + try (InputStream is = new FileInputStream(branchFile)) { + String branchString = IOUtils.toString(is, FileUtil.DEFAULT_CHARSET); + branchSample = JsonUtil.parseJsonObject(branchString); + } + // branch Id generator to util + byte[] rawBranchId = BranchUtil.branchIdGenerator(branchSample); + branchId = HexUtil.toHexString(rawBranchId); + log.debug("Branch Id : {}", branchId); + } + + @Test + public void createStemBranch() { + // Set Receipt + TransactionReceipt receipt = createReceipt(); + setUpReceipt(receipt); + + // add params + JsonObject param = new JsonObject(); + // TODO param add branch and fee + // Get Branch sample in resources + + param.add("branch", branchSample); + param.addProperty("fee", BigInteger.valueOf(1000000)); + + + stemContract.create(param); + assertTrue("Branch Create Success", receipt.isSuccess()); + + String branchKey = String.format("%s%s", PrefixKeyEnum.STEM_BRANCH, branchId); + + receipt.getTxLog().stream().forEach(l -> log.debug(l)); + + assertTrue("Branch Stored", stateStore.contains(branchKey)); + + String branchMetaKey = String.format("%s%s", PrefixKeyEnum.STEM_META, branchId); + assertTrue("Branch Meta Stored", stateStore.contains(branchMetaKey)); + } + + @Test + public void getBranchQuery() { + createStemBranch(); + + JsonObject param = new JsonObject(); + param.addProperty("branchId", branchId); + + + JsonObject branch = stemContract.getBranch(param); + + byte[] rawBranchId = BranchUtil.branchIdGenerator(branch); + String queryBranchId = HexUtil.toHexString(rawBranchId); + assertEquals("branch Id check", branchId, queryBranchId); + } + + @Test + public void getContractQuery() { + createStemBranch(); + + JsonObject param = new JsonObject(); + param.addProperty("branchId", branchId); + + Set contracts = stemContract.getContract(param); + contracts.stream() + .forEach(c -> log.debug(c.getAsJsonObject().get("contractVersion").getAsString())); + assertTrue("Contract Size", contracts.size() == 3); + } + + @Test + public void updateBranchMetaInformation() { + // all meta information is not delete by transaction + createStemBranch(); + // Set new Receipt + TransactionReceipt receipt = createReceipt(); + receipt.setIssuer("101167aaf090581b91c08480f6e559acdd9a3ddd"); + setUpReceipt(receipt); + + JsonObject branchUpdate = new JsonObject(); + branchUpdate.addProperty("name", "NOT UPDATE"); + branchUpdate.addProperty("description", "UPDATE DESCRIPTION"); + + + JsonObject param = new JsonObject(); + param.addProperty("branchId", branchId); + param.add("branch", branchUpdate); + param.addProperty("fee", BigInteger.valueOf(1000000)); + + stemContract.update(param); + + assertEquals("update result", receipt.getStatus(), ExecuteStatus.SUCCESS); + JsonObject metaInfo = stemContract.getBranchMeta(param); + + assertEquals("name did not update meta information", metaInfo.get("name").getAsString(), "YGGDRASH"); + assertEquals("description is updated", metaInfo.get("description").getAsString(), "UPDATE DESCRIPTION"); + } + + @Test + public void updateNotExistBranch() { + + TransactionReceipt receipt = createReceipt(); + setUpReceipt(receipt); + JsonObject branchUpdate = new JsonObject(); + branchUpdate.addProperty("name", "NOT UPDATE"); + branchUpdate.addProperty("description", "UPDATE DESCRIPTION"); + + + JsonObject param = new JsonObject(); + param.addProperty("branchId", branchId); + param.add("branch", branchUpdate); + param.addProperty("fee", BigInteger.valueOf(1000000)); + + stemContract.update(param); + + assertEquals("transaction update is False", receipt.getStatus(), ExecuteStatus.FALSE); + } + + @Test + public void metaDataMerge() { + + JsonObject metaSample = new JsonObject(); + metaSample.addProperty("name", "YGGDRASH"); + metaSample.addProperty("symbol", "YGGDRASH"); + metaSample.addProperty("property", "platform"); + metaSample.addProperty("description", "TRUST-based Multi-dimensional Blockchains"); + + + JsonObject metaUpdate = new JsonObject(); + metaUpdate.addProperty("name", "NOT UPDATE"); + metaUpdate.addProperty("symbol", "NOT UPDATE"); + metaUpdate.addProperty("description", "UPDATE DESCRIPTION"); + + JsonObject metaUpdated = stemContract.metaMerge(metaSample, metaUpdate); + + + assertEquals("Name is not update", + metaUpdated.get("name").getAsString(), "YGGDRASH"); + assertEquals("Symbol is not update", + metaUpdated.get("symbol").getAsString(), "YGGDRASH"); + assertEquals("Property is not update", + metaUpdated.get("property").getAsString(), "platform"); + assertEquals("description is Update", + metaUpdated.get("description").getAsString(), "UPDATE DESCRIPTION"); + + + } + + @Test + public void otherBranchCreate() { + createStemBranch(); + + TransactionReceipt receipt = createReceipt(); + setUpReceipt(receipt); + + JsonObject otherBranch = branchSample.deepCopy(); + otherBranch.addProperty("name", "ETH TO YEED Branch"); + otherBranch.addProperty("symbol", "ETY"); + otherBranch.addProperty("property", "exchange"); + otherBranch.addProperty("timeStamp", "00000166c837f0c9"); + + + JsonObject param = new JsonObject(); + param.add("branch", otherBranch); + param.addProperty("fee", BigInteger.valueOf(10000)); + + byte[] rawBranchId = BranchUtil.branchIdGenerator(otherBranch); + String otherBranchId = HexUtil.toHexString(rawBranchId); + + JsonObject queryParam = new JsonObject(); + queryParam.addProperty("branchId", otherBranchId); + + stemContract.create(param); + + assertEquals("otherBranch Create", receipt.getStatus(), ExecuteStatus.SUCCESS); + + JsonObject queryMeta = stemContract.getBranchMeta(queryParam); + + assertEquals("otehr branch symbol", "ETY", queryMeta.get("symbol").getAsString()); + } + + private TransactionReceipt createReceipt() { + TransactionReceipt receipt = new TransactionReceiptImpl(); + receipt.setIssuer("101167aaf090581b91c08480f6e559acdd9a3ddd"); + return receipt; + } + + private void setUpReceipt(TransactionReceipt receipt) { + try { + txReceiptField.set(stemContract, receipt); + testYeed.setTxReceipt(receipt); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + + +} \ No newline at end of file diff --git a/contracts/stem/src/test/java/io/yggdrash/contract/StemContractValidatorTest.java b/contracts/stem/src/test/java/io/yggdrash/contract/StemContractValidatorTest.java new file mode 100644 index 000000000..2ed289011 --- /dev/null +++ b/contracts/stem/src/test/java/io/yggdrash/contract/StemContractValidatorTest.java @@ -0,0 +1,424 @@ +package io.yggdrash.contract; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.yggdrash.common.contract.vo.dpoa.Validator; +import io.yggdrash.common.crypto.HashUtil; +import io.yggdrash.common.crypto.HexUtil; +import io.yggdrash.common.store.StateStore; +import io.yggdrash.common.store.datasource.HashMapDbSource; +import io.yggdrash.common.utils.BranchUtil; +import io.yggdrash.common.utils.ByteUtil; +import io.yggdrash.common.utils.ContractUtils; +import io.yggdrash.common.utils.FileUtil; +import io.yggdrash.common.utils.JsonUtil; +import io.yggdrash.contract.core.ExecuteStatus; +import io.yggdrash.contract.core.TransactionReceipt; +import io.yggdrash.contract.core.TransactionReceiptImpl; +import io.yggdrash.contract.core.annotation.ContractBranchStateStore; +import io.yggdrash.contract.core.annotation.ContractStateStore; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.InvalidCipherTextException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class StemContractValidatorTest { + + private static final Logger log = LoggerFactory.getLogger(StemContractValidatorTest.class); + private static final StemContract.StemService stemContract = new StemContract.StemService(); + + + private Field txReceiptField; + TestYeed testYeed = new TestYeed(); + + StateStore stateStore; + + TestWallet v1; + TestWallet v2; + TestWallet v3; + TestWallet v4; + + String updateBranchId; + String proposer = "5244d8163ea6fdd62aa08ae878b084faa0b013be"; + + + @Before + public void setUp() throws IllegalAccessException, IOException, InvalidCipherTextException { + // Steup StemContract + stateStore = new StateStore(new HashMapDbSource()); + + List txReceipt = ContractUtils.txReceiptFields(stemContract); + if (txReceipt.size() == 1) { + txReceiptField = txReceipt.get(0); + } + for (Field f : ContractUtils.contractFields(stemContract, ContractStateStore.class)) { + f.setAccessible(true); + f.set(stemContract, stateStore); + } + // 1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e + JsonObject obj = new JsonObject(); + obj.addProperty("address", "1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e"); + assertTrue("setup", BigInteger.ZERO.compareTo(testYeed.balanceOf(obj)) < 0); + + TestBranchStateStore branchStateStore = new TestBranchStateStore(); + branchStateStore.getValidators().getValidatorMap() + .put("81b7e08f65bdf5648606c89998a9cc8164397647", + new Validator("81b7e08f65bdf5648606c89998a9cc8164397647")); + + for (Field f : ContractUtils.contractFields(stemContract, ContractBranchStateStore.class)) { + f.setAccessible(true); + f.set(stemContract, branchStateStore); + } + + String v1Path = getClass() + .getResource("/validatorKeys/5244d8163ea6fdd62aa08ae878b084faa0b013be.json") + .getFile(); + v1 = new TestWallet(new File(v1Path), "Aa1234567890!"); + + String v2Path = getClass() + .getResource("/validatorKeys/a4e063d728ee7a45c5bab3aa2283822d49a9f73a.json") + .getFile(); + v2 = new TestWallet(new File(v2Path), "Password1234!"); + + String v3Path = getClass() + .getResource("/validatorKeys/e38f532685b5e61eca5bc25a0da8ea87d74e671e.json") + .getFile(); + v3 = new TestWallet(new File(v3Path), "Password1234!"); + + String v4Path = getClass() + .getResource("/validatorKeys/f5927c28b66d4bb4b50395662a097370e8cd7e58.json") + .getFile(); + v4 = new TestWallet(new File(v4Path), "Password1234!"); + + // Update Branch is + InputStream testUpdateBranch = getClass().getClassLoader().getResourceAsStream("update-branch.json"); + String branchString = IOUtils.toString(testUpdateBranch, FileUtil.DEFAULT_CHARSET); + JsonObject updateBranchObject = JsonUtil.parseJsonObject(branchString); + byte[] rawBranchId = BranchUtil.branchIdGenerator(updateBranchObject); + updateBranchId = HexUtil.toHexString(rawBranchId); + + JsonObject param = new JsonObject(); + // Get Branch sample in resources + + param.add("branch", updateBranchObject); + param.addProperty("fee", BigInteger.valueOf(1000000)); + + TransactionReceipt receipt = createReceipt(proposer); + receipt.setIssuer(proposer); + setUpReceipt(receipt); + + + stemContract.create(param); + assertTrue("Branch Create Success", receipt.isSuccess()); + } + + @Test + public void updateValidatorsAddValidator() { + // TODO validator can suggest validator and other validator vote to suggest validator + // TODO all message is just one transaction + + // message property + // BRANCH_ID , (20byte) + // BLOCK_HEIGHT (8 BYTE), + // PROPOSER_VALIDATOR (20 byte) + // TARGET_VALIDATOR (20 byte) + // OPERATING_FLAG (1byte) + // SIGNATURE (65 byte) ( N Validators ) + + String targetBranchId = updateBranchId; + Long blockHeight = 100L; + + // add new validator + String targetValidator = "c91e9d46dd4b7584f0b6348ee18277c10fd7cb94"; + String operatingFlag = StemOperation.ADD_VALIDATOR.toValue(); // ADD OR DELETE OR REPLACE + + byte[] message = ByteUtil.merge( + HexUtil.hexStringToBytes(targetBranchId), + ByteUtil.longToBytes(blockHeight), + HexUtil.hexStringToBytes(proposer), + HexUtil.hexStringToBytes(targetValidator), + operatingFlag.getBytes() + ); + // message make to sha3 + message = HashUtil.sha3(message); + log.debug("message Size : {} ", message.length); + assertEquals("message Length : ", message.length, 32); + + log.debug(updateBranchId); + + byte[] signV1 = v1.sign(message, true); + byte[] signV2 = v2.sign(message, true); + byte[] signV3 = v3.sign(message, true); + byte[] signV4 = v4.sign(message, true); + + String[] signed = new String[]{ + HexUtil.toHexString(signV1), HexUtil.toHexString(signV2), + HexUtil.toHexString(signV3), HexUtil.toHexString(signV4) + }; + + // add to params in sign + JsonArray signedArray = new JsonArray(); + Arrays.stream(signed).forEach(sg -> signedArray.add(sg)); + + JsonObject updateBranchValiator = new JsonObject(); + updateBranchValiator.addProperty("branchId", targetBranchId); + updateBranchValiator.addProperty("blockHeight", blockHeight); + updateBranchValiator.addProperty("proposer", proposer); + updateBranchValiator.addProperty("targetValidator", targetValidator); + updateBranchValiator.addProperty("operatingFlag", StemOperation.ADD_VALIDATOR.toValue()); + updateBranchValiator.add("signed", signedArray); + + log.debug(JsonUtil.prettyFormat(updateBranchValiator)); + + // UPDATE Validator Set + TransactionReceipt updateReceipt = createReceipt(proposer); + setUpReceipt(updateReceipt); + + stemContract.updateValidator(updateBranchValiator); + assert updateReceipt.getStatus() == ExecuteStatus.SUCCESS; + + JsonObject validators = stemContract.getValidators(updateBranchId); + log.debug(JsonUtil.prettyFormat(validators)); + + assertTrue("Add new Validator", validators.getAsJsonArray("validators").size() == 5); + } + + @Test + public void updateValidatorsRemoveValidator() { + // TODO remove validator + + String targetBranchId = updateBranchId; + Long blockHeight = 100L; + + // remove exist validator (v2) + String targetValidator = "a4e063d728ee7a45c5bab3aa2283822d49a9f73a"; + String operatingFlag = StemOperation.REMOVE_VALIDATOR.toValue(); // ADD OR DELETE OR REPLACE + + byte[] message = ByteUtil.merge( + HexUtil.hexStringToBytes(targetBranchId), + ByteUtil.longToBytes(blockHeight), + HexUtil.hexStringToBytes(proposer), + HexUtil.hexStringToBytes(targetValidator), + operatingFlag.getBytes() + ); + // message make to sha3 + message = HashUtil.sha3(message); + log.debug("message Size : {} ", message.length); + assertEquals("message Length : ", message.length, 32); + + log.debug(updateBranchId); + + byte[] signV1 = v1.sign(message, true); + //byte[] signV2 = v2.sign(message, true); + byte[] signV3 = v3.sign(message, true); + byte[] signV4 = v4.sign(message, true); + + String[] signed = new String[]{ + HexUtil.toHexString(signV1), HexUtil.toHexString(signV3), HexUtil.toHexString(signV4) + }; + + // add to params in sign + JsonArray signedArray = new JsonArray(); + Arrays.stream(signed).forEach(sg -> signedArray.add(sg)); + + JsonObject updateBranchValiator = new JsonObject(); + updateBranchValiator.addProperty("branchId", targetBranchId); + updateBranchValiator.addProperty("blockHeight", blockHeight); + updateBranchValiator.addProperty("proposer", proposer); + updateBranchValiator.addProperty("targetValidator", targetValidator); + updateBranchValiator.addProperty("operatingFlag", operatingFlag); + updateBranchValiator.add("signed", signedArray); + + log.debug(JsonUtil.prettyFormat(updateBranchValiator)); + + + // UPDATE Validator Set + TransactionReceipt updateReceipt = createReceipt(proposer); + setUpReceipt(updateReceipt); + + stemContract.updateValidator(updateBranchValiator); + assert updateReceipt.getStatus() == ExecuteStatus.SUCCESS; + + JsonObject validators = stemContract.getValidators(updateBranchId); + log.debug(JsonUtil.prettyFormat(validators)); + Set validatorSet = JsonUtil.convertJsonArrayToSet(validators.getAsJsonArray("validators")); + assertTrue("remove Validator", validators.getAsJsonArray("validators").size() == 3); + assertTrue("remove check validator", !validatorSet.contains(targetValidator)); + + } + + @Test + public void updateValidatorsReplaceValidator() { + // TODO remove and add validator + + String targetBranchId = updateBranchId; + Long blockHeight = 100L; + + // remove exist validator (v2) + String targetValidator = "c91e9d46dd4b7584f0b6348ee18277c10fd7cb94"; + String operatingFlag = StemOperation.REPLACE_VALIDATOR.toValue(); // ADD OR DELETE OR REPLACE + + // proposer to target + + byte[] message = ByteUtil.merge( + HexUtil.hexStringToBytes(targetBranchId), + ByteUtil.longToBytes(blockHeight), + HexUtil.hexStringToBytes(proposer), + HexUtil.hexStringToBytes(targetValidator), + operatingFlag.getBytes() + ); + // message make to sha3 + message = HashUtil.sha3(message); + log.debug("message Size : {} ", message.length); + assertEquals("message Length : ", message.length, 32); + + log.debug(updateBranchId); + + byte[] signV1 = v1.sign(message, true); + //byte[] signV2 = v2.sign(message, true); + byte[] signV3 = v3.sign(message, true); + byte[] signV4 = v4.sign(message, true); + + String[] signed = new String[]{ + HexUtil.toHexString(signV1), HexUtil.toHexString(signV3), HexUtil.toHexString(signV4) + }; + + // add to params in sign + JsonArray signedArray = new JsonArray(); + Arrays.stream(signed).forEach(sg -> signedArray.add(sg)); + + JsonObject updateBranchValiator = new JsonObject(); + updateBranchValiator.addProperty("branchId", targetBranchId); + updateBranchValiator.addProperty("blockHeight", blockHeight); + updateBranchValiator.addProperty("proposer", proposer); + updateBranchValiator.addProperty("targetValidator", targetValidator); + updateBranchValiator.addProperty("operatingFlag", operatingFlag); + updateBranchValiator.add("signed", signedArray); + + log.debug(JsonUtil.prettyFormat(updateBranchValiator)); + + + // UPDATE Validator Set + TransactionReceipt updateReceipt = createReceipt(proposer); + setUpReceipt(updateReceipt); + + stemContract.updateValidator(updateBranchValiator); + assert updateReceipt.getStatus() == ExecuteStatus.SUCCESS; + + JsonObject validators = stemContract.getValidators(updateBranchId); + log.debug(JsonUtil.prettyFormat(validators)); + Set validatorSet = JsonUtil.convertJsonArrayToSet(validators.getAsJsonArray("validators")); + assertTrue("remove check validator", validatorSet.contains(targetValidator)); + assertTrue("remove check validator", !validatorSet.contains(proposer)); + + } + + @Test + public void updateValidatorsUpdateValidatorSet() { + // TODO Update validator Set + String targetBranchId = updateBranchId; + Long blockHeight = 100L; + + // remove exist validator (v2) + String operatingFlag = StemOperation.UPDATE_VALIDATOR_SET.toValue(); // ADD OR DELETE OR REPLACE + + JsonObject targetValidatorSet = stemContract.getValidators(updateBranchId); + targetValidatorSet.getAsJsonArray("validators") + .add("101167aaf090581b91c08480f6e559acdd9a3ddd"); + targetValidatorSet.getAsJsonArray("validators") + .add("ffcbff030ecfa17628abdd0ff1990be003da35a2"); + targetValidatorSet.getAsJsonArray("validators") + .add("b0aee21c81bf6057efa9a321916f0f1a12f5c547"); + + log.debug(JsonUtil.prettyFormat(targetValidatorSet)); + + byte[] validatorsByteArray = targetValidatorSet.toString().getBytes(StandardCharsets.UTF_8); + byte[] validatorsSha3 = HashUtil.sha3omit12(validatorsByteArray); + String targetValidator = HexUtil.toHexString(validatorsSha3); + + // proposer to target + + byte[] message = ByteUtil.merge( + HexUtil.hexStringToBytes(targetBranchId), + ByteUtil.longToBytes(blockHeight), + HexUtil.hexStringToBytes(proposer), + HexUtil.hexStringToBytes(targetValidator), + operatingFlag.getBytes() + ); + // message make to sha3 + message = HashUtil.sha3(message); + log.debug("message Size : {} ", message.length); + assertEquals("message Length : ", message.length, 32); + + log.debug(updateBranchId); + + byte[] signV1 = v1.sign(message, true); + byte[] signV2 = v2.sign(message, true); + byte[] signV3 = v3.sign(message, true); + byte[] signV4 = v4.sign(message, true); + + String[] signed = new String[]{ + HexUtil.toHexString(signV1), HexUtil.toHexString(signV2), HexUtil.toHexString(signV3), HexUtil.toHexString(signV4) + }; + + // add to params in sign + JsonArray signedArray = new JsonArray(); + Arrays.stream(signed).forEach(sg -> signedArray.add(sg)); + + JsonObject updateBranchValiator = new JsonObject(); + updateBranchValiator.addProperty("branchId", targetBranchId); + updateBranchValiator.addProperty("blockHeight", blockHeight); + updateBranchValiator.addProperty("proposer", proposer); + updateBranchValiator.addProperty("targetValidator", targetValidator); + updateBranchValiator.addProperty("operatingFlag", operatingFlag); + updateBranchValiator.add("signed", signedArray); + updateBranchValiator.add("validators", targetValidatorSet.getAsJsonArray("validators")); + + log.debug(JsonUtil.prettyFormat(updateBranchValiator)); + + + // UPDATE Validator Set + TransactionReceipt updateReceipt = createReceipt(proposer); + setUpReceipt(updateReceipt); + + stemContract.updateValidator(updateBranchValiator); + assert updateReceipt.getStatus() == ExecuteStatus.SUCCESS; + + JsonObject validators = stemContract.getValidators(updateBranchId); + log.debug(JsonUtil.prettyFormat(validators)); + + assertTrue(targetValidatorSet.getAsJsonArray("validators").size() == 7); + } + + + + private TransactionReceipt createReceipt(String issuer) { + TransactionReceipt receipt = new TransactionReceiptImpl(); + receipt.setIssuer(issuer); + return receipt; + } + + private void setUpReceipt(TransactionReceipt receipt) { + try { + txReceiptField.set(stemContract, receipt); + testYeed.setTxReceipt(receipt); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + +} diff --git a/contracts/stem/src/test/java/io/yggdrash/contract/TestBranchStateStore.java b/contracts/stem/src/test/java/io/yggdrash/contract/TestBranchStateStore.java new file mode 100644 index 000000000..6ffef1e2d --- /dev/null +++ b/contracts/stem/src/test/java/io/yggdrash/contract/TestBranchStateStore.java @@ -0,0 +1,43 @@ +package io.yggdrash.contract; + +import io.yggdrash.common.Sha3Hash; +import io.yggdrash.common.contract.vo.dpoa.ValidatorSet; +import io.yggdrash.common.store.BranchStateStore; + +public class TestBranchStateStore implements BranchStateStore { + ValidatorSet set = new ValidatorSet(); + + @Override + public Long getLastExecuteBlockIndex() { + return null; + } + + @Override + public Sha3Hash getLastExecuteBlockHash() { + return null; + } + + @Override + public Sha3Hash getGenesisBlockHash() { + return null; + } + + @Override + public Sha3Hash getBranchIdHash() { + return null; + } + + @Override + public ValidatorSet getValidators() { + return set; + } + + @Override + public boolean isValidator(String address) { + return set.contains(address); + } + + public void setValidators(ValidatorSet validatorSet) { + this.set = validatorSet; + } +} diff --git a/contracts/stem/src/test/java/io/yggdrash/contract/TestWallet.java b/contracts/stem/src/test/java/io/yggdrash/contract/TestWallet.java new file mode 100644 index 000000000..9101944d6 --- /dev/null +++ b/contracts/stem/src/test/java/io/yggdrash/contract/TestWallet.java @@ -0,0 +1,117 @@ +package io.yggdrash.contract; + +import com.google.gson.JsonObject; +import io.yggdrash.common.crypto.ECKey; +import io.yggdrash.common.crypto.HashUtil; +import io.yggdrash.common.crypto.HexUtil; +import io.yggdrash.common.utils.ByteUtil; +import io.yggdrash.common.utils.FileUtil; +import io.yggdrash.common.utils.JsonUtil; +import io.yggdrash.core.wallet.AesEncrypt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.util.encoders.Hex; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +public class TestWallet { + + private static final Logger log = LoggerFactory.getLogger(TestWallet.class); + + private static final String WALLET_PBKDF2_NAME = "pbkdf2"; + private static final int WALLET_PBKDF2_ITERATION = 262144; + private static final int WALLET_PBKDF2_DKLEN = 32; + private static final String WALLET_PBKDF2_PRF = "hmac-sha256"; + private static final String WALLET_PBKDF2_HMAC_HASH = "KECCAK-256"; + private static final String WALLET_PBKDF2_ALGORITHM = "SHA-256"; + private static final String WALLET_KEY_ENCRYPT_ALGORITHM = "aes-128-cbc"; + + private ECKey key; + private String keyPath; + private String keyName; + private byte[] address; + private byte[] publicKey; + + public TestWallet(File file, String password) throws IOException, InvalidCipherTextException { + decryptKeyFileInit(file, password); + + } + + public byte[] sign(byte[] data, boolean hashed) { + ECKey.ECDSASignature signature = null; + if (hashed) { + signature = key.sign(data); + } else { + signature = key.sign(HashUtil.sha3(data)); + } + return signature.toBinary(); + + } + + private void decryptKeyFileInit(File keyFile, String password) + throws IOException, InvalidCipherTextException { + String json = FileUtil.readFileToString(keyFile, FileUtil.DEFAULT_CHARSET); + JsonObject keyJsonObject = JsonUtil.parseJsonObject(json); + + byte[] salt = Hex.decode(getCryptoJsonObect(keyJsonObject) + .getAsJsonObject("kdfparams") + .get("salt") + .getAsString()); + byte[] kdfPass = HashUtil.pbkdf2( + password.getBytes(), + salt, + WALLET_PBKDF2_ITERATION, + WALLET_PBKDF2_DKLEN, + WALLET_PBKDF2_ALGORITHM); + byte[] encData = Hex.decode(getCryptoJsonObect(keyJsonObject) + .get("ciphertext") + .getAsString()); + + byte[] newMac = HashUtil.hash( + ByteUtil.merge(ByteUtil.parseBytes(kdfPass, 16, 16), encData), + WALLET_PBKDF2_HMAC_HASH); + byte[] mac = Hex.decode(getCryptoJsonObect(keyJsonObject) + .get("mac") + .getAsString()); + if (!Arrays.equals(newMac, mac)) { + throw new InvalidCipherTextException("mac is not valid"); + } + + byte[] iv = Hex.decode(getCryptoJsonObect(keyJsonObject) + .getAsJsonObject("cipherparams") + .get("iv") + .getAsString()); + + byte[] priKey = AesEncrypt.decrypt( + encData, ByteUtil.parseBytes(kdfPass, 0, 16), iv); + this.key = ECKey.fromPrivate(priKey); + this.keyPath = keyPath; + this.keyName = keyName; + this.address = key.getAddress(); + this.publicKey = key.getPubKey(); + } + + private JsonObject getCryptoJsonObect(JsonObject keyJsonObject) { + return keyJsonObject.getAsJsonObject("crypto"); + } + + public boolean verify(byte[] data, byte[] signature, boolean isHashed) { + ECKey.ECDSASignature sig = new ECKey.ECDSASignature(signature); + if (isHashed) { + return key.verify(data, sig); + } else { + return key.verify(HashUtil.sha3(data), sig); + } + + } + + public byte[] getAddress() { + return this.address; + } + + public String getAddressHexString() { + return HexUtil.toHexString(getAddress()); + } +} \ No newline at end of file diff --git a/contracts/stem/src/test/java/io/yggdrash/contract/TestYeed.java b/contracts/stem/src/test/java/io/yggdrash/contract/TestYeed.java new file mode 100644 index 000000000..ce7199751 --- /dev/null +++ b/contracts/stem/src/test/java/io/yggdrash/contract/TestYeed.java @@ -0,0 +1,80 @@ +package io.yggdrash.contract; + +import com.google.gson.JsonObject; +import io.yggdrash.common.contract.standard.CoinStandard; +import io.yggdrash.contract.core.ExecuteStatus; +import io.yggdrash.contract.core.TransactionReceipt; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +public class TestYeed implements CoinStandard { + + Map amount = new HashMap<>(); + private TransactionReceipt txReceipt; + + public TestYeed() { + amount.put("c91e9d46dd4b7584f0b6348ee18277c10fd7cb94", new BigInteger("100000000000")); + amount.put("1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e", new BigInteger("100000000000")); + } + + public void setTxReceipt(TransactionReceipt txReceipt) { + this.txReceipt = txReceipt; + } + + @Override + public BigInteger totalSupply() { + return null; + } + + @Override + public BigInteger balanceOf(JsonObject params) { + return amount.get(params.get("address").getAsString()); + } + + @Override + public BigInteger allowance(JsonObject params) { + return null; + } + + @Override + public TransactionReceipt transfer(JsonObject params) { + String from = txReceipt.getIssuer(); + String to = params.get("to").getAsString(); + BigInteger transferAmount = params.get("amount").getAsBigInteger(); + + if (amount.get(from) != null) { + BigInteger fromAmount = amount.get(from); + BigInteger toAmount = BigInteger.ZERO; + if (amount.containsKey(to)) { + toAmount = amount.get(to); + } + + fromAmount = fromAmount.subtract(transferAmount); + toAmount = toAmount.add(transferAmount); + + if (fromAmount.compareTo(BigInteger.ZERO) >= 0) { + amount.put(from, fromAmount); + amount.put(to, toAmount); + } else { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("has no balance"); + } + } else { + txReceipt.setStatus(ExecuteStatus.FALSE); + txReceipt.addLog("has no balance"); + } + + return txReceipt; + } + + @Override + public TransactionReceipt approve(JsonObject params) { + return null; + } + + @Override + public TransactionReceipt transferFrom(JsonObject params) { + return null; + } +} diff --git a/contracts/stem/src/test/resources/update-branch.json b/contracts/stem/src/test/resources/update-branch.json new file mode 100644 index 000000000..be7cf3dfc --- /dev/null +++ b/contracts/stem/src/test/resources/update-branch.json @@ -0,0 +1,62 @@ +{ + "name": "UPDATE_STEM", + "symbol": "US", + "property": "test", + "description": "TEST UPDATE BRANCH", + "contracts": [ + { + "contractVersion": "1319aef6bf061927e9e26fb19da1020f73e01588", + "isSystem": true, + "init": { + "alloc": { + "101167aaf090581b91c08480f6e559acdd9a3ddd": { + "balance": "1000000000000000000000" + }, + "ffcbff030ecfa17628abdd0ff1990be003da35a2": { + "balance": "1000000000000000000000" + }, + "b0aee21c81bf6057efa9a321916f0f1a12f5c547": { + "balance": "1000000000000000000000" + }, + "1a0cdead3d1d1dbeef848fef9053b4f0ae06db9e": { + "balance": "1000000000000000000000" + }, + "57c6510966903044581c148bb67eb47dbbeebef1": { + "balance": "1000000000000000000000" + }, + "d2a5721e80dc439385f3abc5aab0ac4ed2b1cd95": { + "balance": "1000000000000000000000" + }, + "cee3d4755e47055b530deeba062c5bd0c17eb00f": { + "balance": "994000000000000000000000" + }, + "4e5cbe1d0db35add81e7f2840eeb250b5b469161": { + "balance": "994000000000000000000000" + } + } + }, + "description": "ASSET CONTRACT", + "name": "ASSET" + }, + { + "contractVersion": "f410278dec7b89db97ea7cbf76abdabaabc20486", + "isSystem": true, + "init": { + "validators": [ + "5244d8163ea6fdd62aa08ae878b084faa0b013be", + "a4e063d728ee7a45c5bab3aa2283822d49a9f73a", + "e38f532685b5e61eca5bc25a0da8ea87d74e671e", + "f5927c28b66d4bb4b50395662a097370e8cd7e58" + ] + }, + "name": "DPoA", + "description": "This contract is for a validator." + } + ], + "timestamp": "000001674dc56231", + "consensus": { + "algorithm": "pbft", + "period": "* * * * * *" + }, + "governanceContract": "DPoA" +} \ No newline at end of file diff --git a/contracts/stem/src/test/resources/validatorKeys/5244d8163ea6fdd62aa08ae878b084faa0b013be.json b/contracts/stem/src/test/resources/validatorKeys/5244d8163ea6fdd62aa08ae878b084faa0b013be.json new file mode 100644 index 000000000..efa4a5d8f --- /dev/null +++ b/contracts/stem/src/test/resources/validatorKeys/5244d8163ea6fdd62aa08ae878b084faa0b013be.json @@ -0,0 +1,18 @@ +{ + "address": "5244d8163ea6fdd62aa08ae878b084faa0b013be", + "crypto": { + "cipher": "aes-128-cbc", + "cipherparams": { + "iv": "7398e14adf0b04995e23cfd591f60603" + }, + "ciphertext": "efe9193c898983d3ee4bfd7b9d4cb303fcfa82f9eb9722e9d747acfea424c8e743007bab0c82d120105132671e6e1f3d", + "kdf": "pbkdf2", + "kdfparams": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "6b63bc1467161cad3b09d2071987118f8cdc993c9f8fe2851f387fa37c644597" + }, + "mac": "0bc9765be19d0d1dcda62dac5562e5dcfa36d5eb2dc73e9c3da4e038a775ebf4" + } +} \ No newline at end of file diff --git a/contracts/stem/src/test/resources/validatorKeys/a4e063d728ee7a45c5bab3aa2283822d49a9f73a.json b/contracts/stem/src/test/resources/validatorKeys/a4e063d728ee7a45c5bab3aa2283822d49a9f73a.json new file mode 100644 index 000000000..e0685d7c3 --- /dev/null +++ b/contracts/stem/src/test/resources/validatorKeys/a4e063d728ee7a45c5bab3aa2283822d49a9f73a.json @@ -0,0 +1,18 @@ +{ + "address": "a4e063d728ee7a45c5bab3aa2283822d49a9f73a", + "crypto": { + "cipher": "aes-128-cbc", + "cipherparams": { + "iv": "357d0b74092c29ee645aea9f213a1b5b" + }, + "ciphertext": "cd9413a5da89cea11d4fa22e2c9a2e6ff75c6df602a4286797b56c621538fe550f5a68e369e4a3f4a25f428bbb63ab6f", + "kdf": "pbkdf2", + "kdfparams": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "e1ab7e2c9ececaad5deb735b706a21e50d24b33c3c3874d6ddc6b10157fb0613" + }, + "mac": "a12f4f874869d81e7589e5d08334d9a91f306965bd39fa24f5a558b9b4a5667b" + } +} \ No newline at end of file diff --git a/contracts/stem/src/test/resources/validatorKeys/e38f532685b5e61eca5bc25a0da8ea87d74e671e.json b/contracts/stem/src/test/resources/validatorKeys/e38f532685b5e61eca5bc25a0da8ea87d74e671e.json new file mode 100644 index 000000000..b2647a77d --- /dev/null +++ b/contracts/stem/src/test/resources/validatorKeys/e38f532685b5e61eca5bc25a0da8ea87d74e671e.json @@ -0,0 +1,18 @@ +{ + "address": "e38f532685b5e61eca5bc25a0da8ea87d74e671e", + "crypto": { + "cipher": "aes-128-cbc", + "cipherparams": { + "iv": "3156679ca39eeec1da583b610d511855" + }, + "ciphertext": "3471f45d130920f7559c5d5b0fe94a3ea5849d11e25cbc465955c02b5c14002329f6d78bab667db1bd84d4acdf9052b7", + "kdf": "pbkdf2", + "kdfparams": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "7b615816b7b83f15a07484669381dca6846b655545948182e714765a5194cb02" + }, + "mac": "9059af83bd4bbde43930475e31875446fe0362db7ab3edaa41e7352eecfa532b" + } +} \ No newline at end of file diff --git a/contracts/stem/src/test/resources/validatorKeys/f5927c28b66d4bb4b50395662a097370e8cd7e58.json b/contracts/stem/src/test/resources/validatorKeys/f5927c28b66d4bb4b50395662a097370e8cd7e58.json new file mode 100644 index 000000000..af1ffc03b --- /dev/null +++ b/contracts/stem/src/test/resources/validatorKeys/f5927c28b66d4bb4b50395662a097370e8cd7e58.json @@ -0,0 +1,18 @@ +{ + "address": "f5927c28b66d4bb4b50395662a097370e8cd7e58", + "crypto": { + "cipher": "aes-128-cbc", + "cipherparams": { + "iv": "dacf6a29eb989f6baf7ed9732930289c" + }, + "ciphertext": "3346561da5e03620f520fc39caa0fb400e2f27b699266d9bdeb47ae1d83544b26d05710a7a021483c26b77adb29b2d9b", + "kdf": "pbkdf2", + "kdfparams": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "a9ddf75a22cc3d5945ac8bd61c484976723183f74a357e94c780f2f4d751a28d" + }, + "mac": "fa150f28681c312e0b2cd6fd7c53634c692dd6d308e5c3ad62364dc2a2011c82" + } +} \ No newline at end of file diff --git a/docker/config/nodePri.key b/docker/config/nodePri.key index f0563e919..2ab743566 100644 --- a/docker/config/nodePri.key +++ b/docker/config/nodePri.key @@ -1,18 +1,18 @@ { - "address": "57c6510966903044581c148bb67eb47dbbeebef1", + "address": "75faa5936ee14d7a13bcd0c36873250a0033cee0", "crypto": { "cipher": "aes-128-cbc", "cipherparams": { - "iv": "b5b5d20a41b3e42fcd62110cf0aac294" + "iv": "560cc63d20ca0a3d326abec48d9c3be0" }, - "ciphertext": "9b0fd75e47af9e3edece429e888472d2d6d9943fb6364182b937b42064ad4ddb44fbd06cae33f229b986e3dbc0080718", + "ciphertext": "c9d28b3f7f2d84f62f554bc68b96eaf4f9b759c079b05e0eb03c723c699afa34d5e5826f87d0d1ff192e3e4d29a9ab22", "kdf": "pbkdf2", "kdfparams": { "c": 262144, "dklen": 32, "prf": "hmac-sha256", - "salt": "dca733ee371df4c2c091088a05a9365151e3c9051ab817b379cfd9d46d1fe641" + "salt": "7175fb1235d24662e4130a01c12cb740561300ced7aa199b6c76536bc53213b6" }, - "mac": "e874e92a2c0fad3d0ff716b02eb286163935a865973ef969e2cb565a9026be9a" + "mac": "5a9743e02745960d80b66f479a99304b00d7b306f5bd4d1bc2c990d589077541" } } \ No newline at end of file diff --git a/docker/docker-compose-es.yml b/docker/docker-compose-es.yml index ec5de9cda..890798c7a 100644 --- a/docker/docker-compose-es.yml +++ b/docker/docker-compose-es.yml @@ -1,9 +1,12 @@ version: '2.2' services: - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:6.6.2 - container_name: elasticsearch + es01: + image: docker.elastic.co/elasticsearch/elasticsearch:7.0.1 + container_name: es01 environment: + - node.name=es01 + - discovery.seed_hosts=es02 + - cluster.initial_master_nodes=es01,es02 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" @@ -12,24 +15,44 @@ services: soft: -1 hard: -1 volumes: - - esdata1:/usr/share/elasticsearch/data + - esdata01:/usr/share/elasticsearch/data ports: - 9200:9200 - 9300:9300 networks: - esnet + es02: + image: docker.elastic.co/elasticsearch/elasticsearch:7.0.1 + container_name: es02 + environment: + - node.name=es02 + - discovery.seed_hosts=es01 + - cluster.initial_master_nodes=es01,es02 + - cluster.name=docker-cluster + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + volumes: + - esdata02:/usr/share/elasticsearch/data + networks: + - esnet kibana: - image: docker.elastic.co/kibana/kibana:6.6.2 + image: docker.elastic.co/kibana/kibana:7.0.1 container_name: es-kibana ports: - 5601:5601 environment: - ELASTICSEARCH_HOSTS: http://elasticsearch:9200 + ELASTICSEARCH_HOSTS: http://es02:9200 networks: - esnet volumes: - esdata1: + esdata01: + driver: local + esdata02: driver: local networks: diff --git a/settings.gradle b/settings.gradle index 22842c9a0..b7f78b516 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,3 +10,4 @@ include 'contracts:sample' include 'contracts:dpoa' include 'contracts:coin' include 'contracts:yeed' +include 'contracts:stem' diff --git a/yggdrash-common/src/main/java/io/yggdrash/common/contract/vo/PrefixKeyEnum.java b/yggdrash-common/src/main/java/io/yggdrash/common/contract/vo/PrefixKeyEnum.java index 1a1b0e530..73a5ed7bf 100644 --- a/yggdrash-common/src/main/java/io/yggdrash/common/contract/vo/PrefixKeyEnum.java +++ b/yggdrash-common/src/main/java/io/yggdrash/common/contract/vo/PrefixKeyEnum.java @@ -13,7 +13,11 @@ public enum PrefixKeyEnum { VALIDATORS("vl-"), PROPOSE_INTER_CHAIN("pi-"), PROPOSE_INTER_CHAIN_STATUS("pis-"), - TRANSACTION_CONFIRM("tc-") + TRANSACTION_CONFIRM("tc-"), + STEM_BRANCH("sb-"), + STEM_META("sm-"), + STEM_BRANCH_VALIDATOR("sbv-"), + ; private final String value; diff --git a/yggdrash-common/src/main/java/io/yggdrash/common/utils/BranchUtil.java b/yggdrash-common/src/main/java/io/yggdrash/common/utils/BranchUtil.java new file mode 100644 index 000000000..557e5a7e2 --- /dev/null +++ b/yggdrash-common/src/main/java/io/yggdrash/common/utils/BranchUtil.java @@ -0,0 +1,19 @@ +package io.yggdrash.common.utils; + +import com.google.gson.JsonObject; +import io.yggdrash.common.crypto.HashUtil; + +import static io.yggdrash.common.utils.SerializationUtil.serializeString; + +public class BranchUtil { + + public static byte[] branchIdGenerator(JsonObject jsonObject) { + byte[] branchBytes = serializeString(jsonObject.toString()); + return BranchUtil.branchIdGenerator(branchBytes); + } + + public static byte[] branchIdGenerator(byte[] branchBytes) { + return HashUtil.sha3omit12(branchBytes); + } + +} diff --git a/yggdrash-common/src/main/java/io/yggdrash/common/utils/JsonUtil.java b/yggdrash-common/src/main/java/io/yggdrash/common/utils/JsonUtil.java index 8fb105a3f..40edf063b 100644 --- a/yggdrash-common/src/main/java/io/yggdrash/common/utils/JsonUtil.java +++ b/yggdrash-common/src/main/java/io/yggdrash/common/utils/JsonUtil.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -81,6 +82,14 @@ public static Set convertJsonArrayToSet(JsonArray array) { return set; } + public static JsonArray convertCollectionToJsonArray(Collection c) { + JsonArray array = new JsonArray(); + for (String e : c) { + array.add(e); + } + return array; + } + public static String prettyFormat(String jsonString) { JsonParser parser = new JsonParser(); diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainImpl.java b/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainImpl.java index 4e3ae52cf..be596edff 100644 --- a/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainImpl.java +++ b/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainImpl.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; public class BlockChainImpl implements BlockChain { private static final Logger log = LoggerFactory.getLogger(BlockChainImpl.class); @@ -36,6 +37,7 @@ public class BlockChainImpl implements BlockChain { //private final Map outputStores; //TODO move to gw module private final Consensus consensus; + private final ReentrantLock lock = new ReentrantLock(); public BlockChainImpl(Branch branch, ConsensusBlock genesisBlock, @@ -71,7 +73,7 @@ private void init() { contractManager.reloadInject(); } catch (IllegalAccessException e) { log.error(e.getMessage()); - throw new RuntimeException("contract Inject Fail"); + throw new FailedOperationException("contract Inject Fail"); } } else { // TODO BlockChain ready fails @@ -91,7 +93,6 @@ private void init() { } } - private void initGenesis() { blockChainManager.initGenesis(genesisBlock); addBlock(genesisBlock, false); @@ -160,48 +161,37 @@ public Map getUnConfirmedData() { @Override public ConsensusBlock addBlock(ConsensusBlock nextBlock, boolean broadcast) { - if (blockChainManager.verify(nextBlock) != BusinessError.VALID.toValue()) { - return null; - } + try { + lock.lock(); - // add best Block - branchStore.setBestBlock(nextBlock); - - // run Block Transactions - // TODO run block execute move to other process (or thread) - // TODO last execute block will invoke - if (nextBlock.getIndex() > branchStore.getLastExecuteBlockIndex()) { - BlockRuntimeResult result = contractManager.executeTxs(nextBlock); //TODO Exception - // Save Result - contractManager.commitBlockResult(result); - branchStore.setLastExecuteBlock(nextBlock); - - /* - //Store event //TODO move to gw module - if (outputStores != null && outputStores.size() > 0) { - Map transactionMap = new HashMap<>(); - List txList = nextBlock.getBody().getTransactionList(); - txList.forEach(tx -> { - String txHash = tx.getHash().toString(); - transactionMap.put(txHash, tx.toJsonObjectFromProto()); - }); - - outputStores.forEach((storeType, store) -> { - store.put(nextBlock.toJsonObjectByProto()); - store.put(nextBlock.getHash().toString(), transactionMap); - }); + if (blockChainManager.verify(nextBlock) != BusinessError.VALID.toValue()) { + return null; + } + // add best Block + branchStore.setBestBlock(nextBlock); + + // run Block Transactions + // TODO run block execute move to other process (or thread) + // TODO last execute block will invoke + if (nextBlock.getIndex() > branchStore.getLastExecuteBlockIndex()) { + BlockRuntimeResult result = contractManager.executeTxs(nextBlock); //TODO Exception + // Save Result + contractManager.commitBlockResult(result); + branchStore.setLastExecuteBlock(nextBlock); } - */ - } - // BlockChainManager add nextBlock to the blockStore, set the lastConfirmedBlock to nextBlock, - // and then batch the transactions. - blockChainManager.addBlock(nextBlock); - if (!listenerList.isEmpty() && broadcast) { - listenerList.forEach(listener -> listener.chainedBlock(nextBlock)); + // BlockChainManager add nextBlock to the blockStore, set the lastConfirmedBlock to nextBlock, + // and then batch the transactions. + blockChainManager.addBlock(nextBlock); + if (!listenerList.isEmpty() && broadcast) { + listenerList.forEach(listener -> listener.chainedBlock(nextBlock)); + } + log.debug("Added idx=[{}], tx={}, branch={}, blockHash={}", nextBlock.getIndex(), + nextBlock.getBody().getCount(), getBranchId(), nextBlock.getHash()); + } finally { + lock.unlock(); } - log.debug("Added idx=[{}], tx={}, branch={}, blockHash={}", nextBlock.getIndex(), - nextBlock.getBody().getCount(), getBranchId(), nextBlock.getHash()); + return nextBlock; } diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainSyncManager.java b/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainSyncManager.java index ebb2f0958..88a472360 100644 --- a/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainSyncManager.java +++ b/yggdrash-core/src/main/java/io/yggdrash/core/blockchain/BlockChainSyncManager.java @@ -71,7 +71,6 @@ public boolean syncBlock(BlockChainHandler peerHandler, BlockChain blockChain) { } } catch (InterruptedException | ExecutionException e) { log.debug("[SyncManager] Sync Block ERR occurred: {}", e.getMessage(), e); - Thread.currentThread().interrupt(); } return false; @@ -89,7 +88,12 @@ private void fullSyncBlock(BlockChain blockChain, List peerHa retry = true; } } catch (Exception e) { - log.warn("[SyncManager] Sync Block ERR occurred: {}", e.getCause().getMessage()); + String error = e.getMessage(); + if (e.getCause() != null) { + error = e.getCause().getMessage(); + } + log.warn("[SyncManager] Full Sync Block ERR occurred: {}, from={}", error, + peerHandler.getPeer().getYnodeUri()); } } } @@ -166,7 +170,7 @@ public void catchUpRequest(ConsensusBlock block) { try { blockChain.addBlock(block, false); } catch (Exception e) { - log.warn("Catch up block error={}", e.getMessage()); + log.warn("CatchUp block error={}", e.getMessage()); } } diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/consensus/AbstractConsensusBlock.java b/yggdrash-core/src/main/java/io/yggdrash/core/consensus/AbstractConsensusBlock.java index 093d68edc..1ac6a2977 100644 --- a/yggdrash-core/src/main/java/io/yggdrash/core/consensus/AbstractConsensusBlock.java +++ b/yggdrash-core/src/main/java/io/yggdrash/core/consensus/AbstractConsensusBlock.java @@ -83,6 +83,11 @@ public long getLength() { return block.getLength(); } + @Override + public int getSerializedSize() { + return toBinary().length; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/consensus/ConsensusBlock.java b/yggdrash-core/src/main/java/io/yggdrash/core/consensus/ConsensusBlock.java index 352f95f3e..2642570ab 100644 --- a/yggdrash-core/src/main/java/io/yggdrash/core/consensus/ConsensusBlock.java +++ b/yggdrash-core/src/main/java/io/yggdrash/core/consensus/ConsensusBlock.java @@ -12,4 +12,5 @@ public interface ConsensusBlock extends Block, ProtoObject { JsonObject toJsonObjectByProto(); + int getSerializedSize(); } diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContract.java b/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContract.java deleted file mode 100644 index b6db9ec4a..000000000 --- a/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContract.java +++ /dev/null @@ -1,340 +0,0 @@ - -package io.yggdrash.core.contract; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import io.yggdrash.common.contract.Contract; -import io.yggdrash.contract.core.ExecuteStatus; -import io.yggdrash.contract.core.TransactionReceipt; -import io.yggdrash.contract.core.annotation.ContractQuery; -import io.yggdrash.contract.core.annotation.ContractStateStore; -import io.yggdrash.contract.core.annotation.ContractTransactionReceipt; -import io.yggdrash.contract.core.annotation.Genesis; -import io.yggdrash.contract.core.annotation.InvokeTransaction; -import io.yggdrash.contract.core.store.ReadWriterStore; -import io.yggdrash.core.blockchain.Branch; -import io.yggdrash.core.blockchain.BranchId; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceEvent; -import org.osgi.framework.ServiceListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.Set; - -import static io.yggdrash.common.config.Constants.BRANCH_ID; -import static io.yggdrash.common.config.Constants.TX_ID; - - -public class StemContract implements BundleActivator, ServiceListener { - private static final Logger log = LoggerFactory.getLogger(StemContract.class); - //private ServiceRegistration registration; - - @Override - public void start(BundleContext context) { - log.info("⚪ Start stem contract"); - - //Find for service in another bundle - Hashtable props = new Hashtable<>(); - props.put("YGGDRASH", "Stem"); - context.registerService(StemService.class.getName(), new StemService(), props); - //Register our service in the bundle context using the name. - //registration = context.registerService(StemService.class.getName(), new StemService(), props); - } - - @Override - public void stop(BundleContext context) { - log.info("⚫ Stop stem contract"); - //TODO Why don't unregister the service? - //registration.unregister(); - } - - @Override - public void serviceChanged(ServiceEvent event) { - // Not implemented. - } - - public static class StemService implements Contract { - private static final Logger log = LoggerFactory.getLogger(StemContract.class); - private final String branchIdListKey = "BRANCH_ID_LIST"; - - @ContractStateStore - ReadWriterStore state; - - - @ContractTransactionReceipt - TransactionReceipt txReceipt; - - @Genesis - @InvokeTransaction // TODO remove InvokeTransaction - public TransactionReceipt init(JsonObject param) { - txReceipt = create(param); - log.info("[StemContract | genesis] SUCCESS! param => {}", param); - return txReceipt; - } - - /** - * Returns the id of a registered branch - * - * @param params branch : The branch.json to register on the stem - */ - @InvokeTransaction - public TransactionReceipt create(JsonObject params) { - StemContractStateValue stateValue; - try { - stateValue = StemContractStateValue.of(params); - BranchId branchId = stateValue.getBranchId(); - if (!isBranchExist(branchId.toString()) - && isBranchIdValid(branchId, stateValue)) { - try { - stateValue.init(); - setStateValue(stateValue, params); - addBranchId(branchId); - state.put(branchId.toString(), stateValue.getJson()); - addTxId(branchId); - - txReceipt.setStatus(ExecuteStatus.SUCCESS); - log.info("[StemContract | create] branchId => {}", branchId); - log.info("[StemContract | create] branch => {}", stateValue.getJson()); - } catch (Exception e) { - txReceipt.setStatus(ExecuteStatus.FALSE); - } - } - } catch (Exception e) { - log.warn("Failed to convert Branch = {}", params); - } - return txReceipt; - } - - /** - * Returns the id of a updated branch - * - * @param params branchId The Id of the branch to update - * branch The branch.json to update on the stem - */ - @InvokeTransaction - public TransactionReceipt update(JsonObject params) { - StemContractStateValue stateValue; - try { - String preBranchId = params.get(BRANCH_ID).getAsString(); - JsonObject preBranchJson = state.get(preBranchId); - stateValue = StemContractStateValue.of(preBranchJson); - BranchId branchId = stateValue.getBranchId(); - - if (isBranchExist(branchId.toString())) { - try { - setStateValue(stateValue, params); - addBranchId(branchId); - state.put(branchId.toString(), stateValue.getJson()); - addTxId(branchId); - - txReceipt.setStatus(ExecuteStatus.SUCCESS); - log.info("[StemContract | update] branchId => {}", branchId); - log.info("[StemContract | update] branch => {}", stateValue.getJson()); - } catch (Exception e) { - txReceipt.setStatus(ExecuteStatus.FALSE); - } - } - } catch (Exception e) { - log.warn("Failed to convert Branch = {}", params); - } - - return txReceipt; - } - - /** - * fee = fee - transaction size fee - * tx size fee = txSize / 1mbyte - * 1mbyte to 1yeed - * - * @param stateValue params - */ - private void setStateValue(StemContractStateValue stateValue, JsonObject params) { - if (params.has("fee") && txReceipt.getTxSize() != null) { - BigDecimal fee = params.get("fee").getAsBigDecimal(); - BigDecimal txSize = BigDecimal.valueOf(txReceipt.getTxSize()); - BigDecimal txFee = txSize.divide(BigDecimal.valueOf(1000000)); - BigDecimal resultFee = fee.subtract(txFee); - BigDecimal remainFee = feeState(stateValue); - - if (remainFee.longValue() > 0) { - stateValue.setFee(resultFee.add(remainFee)); - } - } - } - - /** - * Returns boolean - * - * @param branchId - * */ - public void messageCall(BranchId branchId) { - // TODO message call to contract - // TODO isEnoughFee - } - - /** - * Returns a list contains all branch id - * - * @return list of all branch id - */ - @ContractQuery - public Set getBranchIdList() { - JsonObject branchList = state.get(branchIdListKey); - if (branchList == null) { - return Collections.emptySet(); - } - JsonArray branchIds = branchList.getAsJsonArray("branchIds"); - Set branchIdSet = new HashSet<>(); - for (JsonElement branchId : branchIds) { - StemContractStateValue stateValue = - getBranchStateValue(branchId.getAsString()); - if (isEnoughFee(stateValue)) { - branchIdSet.add(branchId.getAsString()); - } - } - return branchIdSet; - } - - /** - * @param params branch id - * - * @return branch json object - */ - @ContractQuery - public JsonObject getBranch(JsonObject params) { - String branchId = params.get(BRANCH_ID).getAsString(); - StemContractStateValue stateValue = getBranchStateValue(branchId); - - if (isBranchExist(branchId) && isEnoughFee(stateValue)) { - stateValue.setFee(feeState(stateValue)); - stateValue.setBlockHeight(txReceipt.getBlockHeight()); - return stateValue.getJson(); - } - // TODO fee not enough mesaage - return new JsonObject(); - } - - /** - * @param params transaction id - * - * @return branch id - */ - @ContractQuery - public String getBranchIdByTxId(JsonObject params) { - String txId = params.get(TX_ID).getAsString(); - JsonObject branchIdJson = state.get(txId); - if (branchIdJson != null && branchIdJson.has("branchId")) { - String branchId = branchIdJson.get("branchId").getAsString(); - StemContractStateValue stateValue = getBranchStateValue(branchId); - if (isBranchExist(branchId) && isEnoughFee(stateValue)) { - return branchIdJson.get("branchId").getAsString(); - } - } - return ""; - } - - /** - * @param params branch id - * - * @return contract json object - */ - @ContractQuery - public Set getContract(JsonObject params) { - String branchId = params.get(BRANCH_ID).getAsString(); - Set contractSet = new HashSet<>(); - StemContractStateValue stateValue = getBranchStateValue(branchId); - - if (isBranchExist(branchId) && isEnoughFee(stateValue)) { - JsonArray contracts = getBranchStateValue(branchId).getJson() - .getAsJsonArray("contracts"); - for (JsonElement c : contracts) { - contractSet.add(c); - } - } - return contractSet; - } - - /** - * @param params branch id - * - * @return fee state - */ - public BigDecimal feeState(JsonObject params) { - String branchId = params.get(BRANCH_ID).getAsString(); - StemContractStateValue stateValue = getBranchStateValue(branchId); - BigDecimal result = BigDecimal.ZERO; - if (isBranchExist(branchId)) { - Long currentHeight = txReceipt.getBlockHeight(); - Long createPointHeight = stateValue.getBlockHeight(); - Long height = currentHeight - createPointHeight; - - //1block to 1yeed - BigDecimal currentFee = stateValue.getFee(); - result = currentFee.subtract(BigDecimal.valueOf(height)); - } - return result.longValue() > 0 ? result : BigDecimal.ZERO; - } - - private BigDecimal feeState(StemContractStateValue stateValue) { - BigDecimal currentFee = stateValue.getFee(); - if (currentFee.longValue() > 0) { - Long currentHeight = txReceipt.getBlockHeight(); - Long createPointHeight = stateValue.getBlockHeight(); - Long overTimeHeight = currentHeight - createPointHeight; - return currentFee.subtract(BigDecimal.valueOf(overTimeHeight)); - } - return BigDecimal.ZERO; - } - - private Boolean isEnoughFee(StemContractStateValue stateValue) { - return feeState(stateValue).longValue() > 0; - } - - private boolean isBranchExist(String branchId) { - return state.contains(branchId); - } - - private void addBranchId(BranchId newBranchId) { - if (!isBranchExist(newBranchId.toString())) { - JsonArray branchIds = new JsonArray(); - for (String branchId : getBranchIdList()) { - branchIds.add(branchId); - } - JsonObject obj = new JsonObject(); - branchIds.add(newBranchId.toString()); - obj.add("branchIds", branchIds); - state.put(branchIdListKey, obj); - - } - } - - private void addTxId(BranchId branchId) { - if (isBranchExist(branchId.toString()) - && txReceipt.getTxId() != null) { - JsonObject bid = new JsonObject(); - bid.addProperty("branchId", branchId.toString()); - state.put(txReceipt.getTxId(), bid); - } - } - - private boolean isBranchIdValid(BranchId branchId, Branch branch) { - return branchId.equals(branch.getBranchId()); - } - - private StemContractStateValue getBranchStateValue(String branchId) { - JsonObject json = state.get(branchId); - if (json == null) { - return null; - } else { - return new StemContractStateValue(json); - } - } - } -} \ No newline at end of file diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContractStateValue.java b/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContractStateValue.java deleted file mode 100644 index fdc9a49be..000000000 --- a/yggdrash-core/src/main/java/io/yggdrash/core/contract/StemContractStateValue.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.yggdrash.core.contract; - -import com.google.gson.JsonObject; -import io.yggdrash.core.blockchain.Branch; - -import java.math.BigDecimal; - -/** - * updatable branch of stem contract - * - */ -public class StemContractStateValue extends Branch { - - private static BigDecimal fee; - private static Long blockHeight; - - StemContractStateValue(JsonObject json) { - super(json); - } - - public void init() { - setFee(BigDecimal.ZERO); - setBlockHeight(0L); - } - - public BigDecimal getFee() { - return fee; - } - - public void setFee(BigDecimal fee) { - this.fee = fee; - getJson().addProperty("fee", fee); - } - - public Long getBlockHeight() { - return blockHeight; - } - - public void setBlockHeight(Long blockHeight) { - this.blockHeight = blockHeight; - getJson().addProperty("blockHeight", blockHeight); - } - - public static StemContractStateValue of(JsonObject json) { - return new StemContractStateValue(json.deepCopy()); - } - -} \ No newline at end of file diff --git a/yggdrash-core/src/main/java/io/yggdrash/core/net/BlockServiceConsumer.java b/yggdrash-core/src/main/java/io/yggdrash/core/net/BlockServiceConsumer.java index 514e30ab8..1fff0aaed 100644 --- a/yggdrash-core/src/main/java/io/yggdrash/core/net/BlockServiceConsumer.java +++ b/yggdrash-core/src/main/java/io/yggdrash/core/net/BlockServiceConsumer.java @@ -108,7 +108,7 @@ private void updateBlockList(BranchId branchId, long offset, long limit, List Limit.BLOCK_SYNC_SIZE) { return; } diff --git a/yggdrash-core/src/main/resources/branch-yggdrash.json b/yggdrash-core/src/main/resources/branch-yggdrash.json index ea3fbe42b..61507b139 100644 --- a/yggdrash-core/src/main/resources/branch-yggdrash.json +++ b/yggdrash-core/src/main/resources/branch-yggdrash.json @@ -85,5 +85,6 @@ "consensus": { "algorithm": "pbft", "period": "* * * * * *" - } + }, + "governanceContract": "DPoA" } \ No newline at end of file diff --git a/yggdrash-core/src/test/java/io/yggdrash/BlockChainTestUtils.java b/yggdrash-core/src/test/java/io/yggdrash/BlockChainTestUtils.java index 8d75be49e..1790aea41 100644 --- a/yggdrash-core/src/test/java/io/yggdrash/BlockChainTestUtils.java +++ b/yggdrash-core/src/test/java/io/yggdrash/BlockChainTestUtils.java @@ -59,7 +59,7 @@ private BlockChainTestUtils() { } static { - try (InputStream is = new FileInputStream(TestConstants.BRANCH_FILE)) { + try (InputStream is = new FileInputStream(TestConstants.branchFile)) { genesis = GenesisBlock.of(is); } catch (Exception e) { throw new InvalidSignatureException(e); diff --git a/yggdrash-core/src/test/java/io/yggdrash/TestConstants.java b/yggdrash-core/src/test/java/io/yggdrash/TestConstants.java index e741b46e8..9728cb759 100644 --- a/yggdrash-core/src/test/java/io/yggdrash/TestConstants.java +++ b/yggdrash-core/src/test/java/io/yggdrash/TestConstants.java @@ -38,7 +38,7 @@ public class TestConstants { static ContractVersion STEM_CONTRACT; public static ContractVersion YEED_CONTRACT; public static Branch TEST_BRANCH; - public static final File BRANCH_FILE; + public static File branchFile; public static final String TRANSFER_TO = "e1980adeafbb9ac6c9be60955484ab1547ab0b76"; @@ -50,12 +50,16 @@ public class TestConstants { private static final Wallet wallet; - private TestConstants() {} + private TestConstants() { + } static { try { wallet = new Wallet(new DefaultConfig(), "Password1234!"); - BRANCH_FILE = new File("../yggdrash-core/src/main/resources", "branch-yggdrash.json"); + branchFile = new File("../yggdrash-core/src/main/resources", "branch-yggdrash.json"); + if (!branchFile.exists()) { + branchFile = new File("yggdrash-core/src/main/resources", "branch-yggdrash.json"); + } } catch (Exception e) { throw new InvalidSignatureException(e); } @@ -66,7 +70,7 @@ public static BranchId yggdrash() { return YGGDRASH_BRANCH_ID; } - try (InputStream is = new FileInputStream(BRANCH_FILE)) { + try (InputStream is = new FileInputStream(branchFile)) { Branch yggdrashBranch = Branch.of(is); TEST_BRANCH = yggdrashBranch; YGGDRASH_BRANCH_ID = yggdrashBranch.getBranchId(); diff --git a/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/BranchTest.java b/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/BranchTest.java index 25e41adac..7d87e6168 100644 --- a/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/BranchTest.java +++ b/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/BranchTest.java @@ -70,7 +70,7 @@ public void defaultTest() { @Test public void loadTest() throws IOException { - String genesisString = FileUtil.readFileToString(TestConstants.BRANCH_FILE, FileUtil.DEFAULT_CHARSET); + String genesisString = FileUtil.readFileToString(TestConstants.branchFile, FileUtil.DEFAULT_CHARSET); JsonObject branch = new JsonParser().parse(genesisString).getAsJsonObject(); Branch yggdrashBranch = Branch.of(branch); Assert.assertEquals("YGGDRASH", yggdrashBranch.getName()); @@ -79,11 +79,11 @@ public void loadTest() throws IOException { @Test public void generatorGenesisBlock() throws IOException { - String genesisString = FileUtil.readFileToString(TestConstants.BRANCH_FILE, FileUtil.DEFAULT_CHARSET); + String genesisString = FileUtil.readFileToString(TestConstants.branchFile, FileUtil.DEFAULT_CHARSET); JsonObject branch = new JsonParser().parse(genesisString).getAsJsonObject(); Branch yggdrashBranch = Branch.of(branch); - FileInputStream inputBranch = new FileInputStream(TestConstants.BRANCH_FILE); + FileInputStream inputBranch = new FileInputStream(TestConstants.branchFile); GenesisBlock block = GenesisBlock.of(inputBranch); Assert.assertEquals(0, block.getBlock().getIndex()); Assert.assertEquals(yggdrashBranch.getName(), block.getBranch().getName()); diff --git a/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/genesis/BranchLoaderTest.java b/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/genesis/BranchLoaderTest.java index 82a4303d1..acd3dc389 100644 --- a/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/genesis/BranchLoaderTest.java +++ b/yggdrash-core/src/test/java/io/yggdrash/core/blockchain/genesis/BranchLoaderTest.java @@ -36,7 +36,7 @@ public class BranchLoaderTest { @Before public void setUpBranch() throws IOException { - String genesisString = FileUtil.readFileToString(TestConstants.BRANCH_FILE, FileUtil.DEFAULT_CHARSET); + String genesisString = FileUtil.readFileToString(TestConstants.branchFile, FileUtil.DEFAULT_CHARSET); JsonObject branchJson = new JsonParser().parse(genesisString).getAsJsonObject(); this.branch = Branch.of(branchJson); } diff --git a/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractStateValueTest.java b/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractStateValueTest.java deleted file mode 100644 index 3e4347373..000000000 --- a/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractStateValueTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.yggdrash.core.contract; - -import com.google.gson.JsonObject; -import io.yggdrash.ContractTestUtils; -import org.junit.Before; -import org.junit.Test; - -import java.math.BigDecimal; - -import static org.junit.Assert.assertEquals; - -public class StemContractStateValueTest { - - private StemContractStateValue stateValue; - - @Before - public void setUp() { - JsonObject json = ContractTestUtils.createSampleBranchJson(); - this.stateValue = StemContractStateValue.of(json); - } - - @Test - public void setFeeTest() { - stateValue.setFee(BigDecimal.valueOf(1000));; - assertEquals(BigDecimal.valueOf(1000), stateValue.getFee()); - } - - @Test - public void setBlockHeightTest() { - Long height = 3L; - stateValue.setBlockHeight(height); - assertEquals(height, stateValue.getBlockHeight()); - - Long b = stateValue.getJson().get("blockHeight").getAsLong(); - assertEquals(b, stateValue.getBlockHeight()); - } -} \ No newline at end of file diff --git a/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractTest.java b/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractTest.java deleted file mode 100644 index 25913c66b..000000000 --- a/yggdrash-core/src/test/java/io/yggdrash/core/contract/StemContractTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2018 Akashic Foundation - * - * 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.yggdrash.core.contract; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import io.yggdrash.ContractTestUtils; -import io.yggdrash.TestConstants; -import io.yggdrash.common.store.StateStore; -import io.yggdrash.common.store.datasource.HashMapDbSource; -import io.yggdrash.common.utils.ContractUtils; -import io.yggdrash.contract.core.TransactionReceipt; -import io.yggdrash.contract.core.TransactionReceiptImpl; -import io.yggdrash.contract.core.annotation.ContractStateStore; -import io.yggdrash.core.blockchain.Branch; -import io.yggdrash.core.blockchain.BranchBuilder; -import io.yggdrash.core.blockchain.BranchId; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.List; -import java.util.Set; - -import static io.yggdrash.common.config.Constants.BRANCH_ID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertTrue; - - -public class StemContractTest { - - private static final StemContract.StemService stemContract = new StemContract.StemService(); - - private StemContractStateValue stateValue; - private Field txReceiptField; - private StateStore stateStore; - - @Before - public void setUp() throws IllegalAccessException { - stateStore = new StateStore(new HashMapDbSource()); - - JsonObject params = ContractTestUtils.createSampleBranchJson(); - stateValue = StemContractStateValue.of(params); - TransactionReceipt receipt = createReceipt(); - List txReceipt = ContractUtils.txReceiptFields(stemContract); - if (txReceipt.size() == 1) { - txReceiptField = txReceipt.get(0); - } - for (Field f : ContractUtils.contractFields(stemContract, ContractStateStore.class)) { - f.setAccessible(true); - f.set(stemContract, stateStore); - } - - try { - txReceiptField.set(stemContract, receipt); - stemContract.init(params); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - @Test - public void getBranchListTest() { - Set branchIdList = stemContract.getBranchIdList(); - if (!branchIdList.isEmpty()) { - assertThat(branchIdList).containsOnly(stateValue.getBranchId().toString()); - } - } - - @Test - public void createTest() { - String description = "ETH TO YEED"; - JsonObject params = getEthToYeedBranch(description); - TransactionReceipt receipt = createReceipt(); - - try { - txReceiptField.set(stemContract, receipt); - stemContract.create(params); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - assertThat(receipt.isSuccess()).isTrue(); - - BranchId branchId = Branch.of(params).getBranchId(); - JsonObject saved = stateStore.get(branchId.toString()); - assertThat(saved).isNotNull(); - assertThat(saved.get("description").getAsString()).isEqualTo(description); - } - - @Test - public void updateTest() { - JsonObject params = createUpdateParams(); - TransactionReceipt receipt = createReceipt(); - - try { - txReceiptField.set(stemContract, receipt); - receipt = stemContract.update(params); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - assertTrue(receipt.isSuccess()); - /* ========================================================= */ - - JsonObject params2 = createUpdateParams2(); - TransactionReceipt receipt2 = createReceipt(); - - try { - txReceiptField.set(stemContract, receipt); - receipt2 = stemContract.update(params2); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - - assertTrue(receipt2.isSuccess()); - } - - private JsonObject createUpdateParams() { - JsonObject params = new JsonObject(); - params.addProperty(BRANCH_ID, stateValue.getBranchId().toString()); - params.addProperty("fee", BigDecimal.valueOf(1000)); - return params; - } - - private JsonObject createUpdateParams2() { - JsonObject params = new JsonObject(); - params.addProperty(BRANCH_ID, stateValue.getBranchId().toString()); - params.addProperty("fee", BigDecimal.valueOf(2000)); - return params; - } - - private static JsonObject getEthToYeedBranch(String description) { - String name = "Ethereum TO YEED"; - String symbol = "ETH TO YEED"; - String property = "exchange"; - String timeStamp = "00000166c837f0c9"; - - String consensusString = new StringBuilder() - .append("{\"consensus\": {\n") - .append(" \"algorithm\": \"pbft\",\n") - .append(" \"period\": \"* * * * * *\"\n") - .append(" \n}") - .append(" }").toString(); - JsonObject consensus = new Gson().fromJson(consensusString, JsonObject.class); - - JsonObject branchJson = BranchBuilder.builder() - .setName(name) - .setDescription(description) - .setSymbol(symbol) - .setProperty(property) - .setTimeStamp(timeStamp) - .setConsensus(consensus) - .buildJson(); - - branchJson.addProperty("fee", BigInteger.valueOf(100)); - return branchJson; - } - - private TransactionReceipt createReceipt() { - TransactionReceipt receipt = new TransactionReceiptImpl(); - receipt.setIssuer(TestConstants.wallet().getHexAddress()); - return receipt; - } -} \ No newline at end of file diff --git a/yggdrash-node/src/main/java/io/yggdrash/node/config/BranchConfiguration.java b/yggdrash-node/src/main/java/io/yggdrash/node/config/BranchConfiguration.java index 4742cd3b7..ba1d14721 100644 --- a/yggdrash-node/src/main/java/io/yggdrash/node/config/BranchConfiguration.java +++ b/yggdrash-node/src/main/java/io/yggdrash/node/config/BranchConfiguration.java @@ -131,36 +131,43 @@ private BlockChain createBranch(GenesisBlock genesis, ContractPolicyLoader polic .setConsensusAlgorithm(consensus.getAlgorithm()) .setBlockStoreFactory(ValidatorService.blockStoreFactory()); - ContractStore contractStore = storeBuilder.buildContractStore(); - BlockChainManager blockChainManager = new BlockChainManagerImpl( - storeBuilder.buildBlockStore(), - storeBuilder.buildTransactionStore(), - contractStore.getTransactionReceiptStore()); - ContractManager contractManager = ContractManagerBuilder.newInstance() - .withFrameworkFactory(policyLoader.getFrameworkFactory()) - .withContractManagerConfig(policyLoader.getContractManagerConfig()) - .withBranchId(branchId.toString()) - .withContractStore(contractStore) - .withConfig(storeBuilder.getConfig()) - .withSystemProperties(systemProperties) - .build(); - BlockChain bc = BlockChainBuilder.newBuilder() - .setGenesis(genesis) - .setBranchStore(contractStore.getBranchStore()) - .setBlockChainManager(blockChainManager) - .setContractManager(contractManager) - .setFactory(ValidatorService.factory()) - .build(); - - log.info("Branch is Ready {}", bc.getBranchId()); - - return bc; + BlockChain blockChain = getBlockChain(genesis, storeBuilder, policyLoader, branchId, systemProperties); + + log.info("Branch is Ready {}", blockChain.getBranchId()); + + return blockChain; } catch (Exception e) { log.warn(e.getMessage(), e); return null; } } + static BlockChain getBlockChain(GenesisBlock genesis, StoreBuilder storeBuilder, + ContractPolicyLoader policyLoader, BranchId branchId, + SystemProperties systemProperties) { + ContractStore contractStore = storeBuilder.buildContractStore(); + BlockChainManager blockChainManager = new BlockChainManagerImpl( + storeBuilder.buildBlockStore(), + storeBuilder.buildTransactionStore(), + contractStore.getTransactionReceiptStore()); + ContractManager contractManager = ContractManagerBuilder.newInstance() + .withFrameworkFactory(policyLoader.getFrameworkFactory()) + .withContractManagerConfig(policyLoader.getContractManagerConfig()) + .withBranchId(branchId.toString()) + .withContractStore(contractStore) + .withConfig(storeBuilder.getConfig()) + .withSystemProperties(systemProperties) + .build(); + + return BlockChainBuilder.newBuilder() + .setGenesis(genesis) + .setBranchStore(contractStore.getBranchStore()) + .setBlockChainManager(blockChainManager) + .setContractManager(contractManager) + .setFactory(ValidatorService.factory()) + .build(); + } + /** * Scheduling Beans */ diff --git a/yggdrash-node/src/main/java/io/yggdrash/node/config/NetworkConfiguration.java b/yggdrash-node/src/main/java/io/yggdrash/node/config/NetworkConfiguration.java index 5d0ce92c9..60d5f0705 100644 --- a/yggdrash-node/src/main/java/io/yggdrash/node/config/NetworkConfiguration.java +++ b/yggdrash-node/src/main/java/io/yggdrash/node/config/NetworkConfiguration.java @@ -33,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @@ -79,6 +80,7 @@ public PeerNetwork peerNetwork(PeerTableGroup peerTableGroup, PeerDialer peerDia * Scheduling Beans */ @Bean + @ConditionalOnExpression("'${yggdrash.node.validator:false}' == 'false'") PeerTask peerTask() { return new PeerTask(); } diff --git a/yggdrash-node/src/main/java/io/yggdrash/node/config/ValidatorConfiguration.java b/yggdrash-node/src/main/java/io/yggdrash/node/config/ValidatorConfiguration.java index 979122308..62278176f 100644 --- a/yggdrash-node/src/main/java/io/yggdrash/node/config/ValidatorConfiguration.java +++ b/yggdrash-node/src/main/java/io/yggdrash/node/config/ValidatorConfiguration.java @@ -6,9 +6,6 @@ import io.yggdrash.common.config.Constants; import io.yggdrash.common.config.DefaultConfig; import io.yggdrash.core.blockchain.BlockChain; -import io.yggdrash.core.blockchain.BlockChainBuilder; -import io.yggdrash.core.blockchain.BlockChainManager; -import io.yggdrash.core.blockchain.BlockChainManagerImpl; import io.yggdrash.core.blockchain.BranchGroup; import io.yggdrash.core.blockchain.BranchId; import io.yggdrash.core.blockchain.SystemProperties; @@ -47,8 +44,7 @@ public Map> validatorServiceMap(BranchGroup bra for (File branchPath : Objects.requireNonNull(validatorPath.listFiles())) { - BranchId branchId = parseBranchId(branchPath); - BlockChain branch = branchGroup.getBranch(branchId); + BlockChain branch = getBranch(branchPath, branchGroup); if (branch == null) { log.warn("Not found branch for [{}]", branchPath); continue; @@ -57,20 +53,22 @@ public Map> validatorServiceMap(BranchGroup bra List validatorServiceList = loadValidatorService(branchPath, genesis, branch.getConsensus(), defaultConfig, systemProperties, policyLoader); - validatorServiceMap.put(branchId, validatorServiceList); + validatorServiceMap.put(branch.getBranchId(), validatorServiceList); } return validatorServiceMap; } - private BranchId parseBranchId(File branchPath) { + private BlockChain getBranch(File branchPath, BranchGroup branchGroup) { String branchName = branchPath.getName(); if (branchName.length() != Constants.BRANCH_HEX_LENGTH || !branchName.matches("^[0-9a-fA-F]+$")) { return null; } - return new BranchId(new Sha3Hash(branchPath.getName())); + BranchId branchId = new BranchId(new Sha3Hash(branchPath.getName())); + + return branchGroup.getBranch(branchId); } private List loadValidatorService(File branchPath, GenesisBlock genesis, Consensus consensus, @@ -80,6 +78,10 @@ private List loadValidatorService(File branchPath, GenesisBloc List validatorServiceList = new ArrayList<>(); for (File validatorServicePath : Objects.requireNonNull(branchPath.listFiles())) { File validatorConfFile = new File(validatorServicePath, "validator.conf"); + if (!validatorConfFile.exists()) { + continue; + } + Config referenceConfig = ConfigFactory.parseFile(validatorConfFile); Config config = defaultConfig.getConfig().withFallback(referenceConfig); DefaultConfig validatorConfig = new DefaultConfig(config, defaultConfig.isProductionMode()); @@ -87,24 +89,15 @@ private List loadValidatorService(File branchPath, GenesisBloc validatorConfig.getString("yggdrash.validator.port"), validatorConfig.getString("yggdrash.validator.key.path")); try { - + BranchId branchId = genesis.getBranch().getBranchId(); StoreBuilder storeBuilder = StoreBuilder.newBuilder() .setBranchId(genesis.getBranch().getBranchId()) .setConfig(validatorConfig) .setConsensusAlgorithm(consensus.getAlgorithm()) .setBlockStoreFactory(ValidatorService.blockStoreFactory()); - BlockChainManager blockChainManager = new BlockChainManagerImpl( - storeBuilder.buildBlockStore(), - storeBuilder.buildTransactionStore(), - storeBuilder.buildTransactionReceiptStore()); - - BlockChain blockChain = BlockChainBuilder.newBuilder() - .setGenesis(genesis) - .setBranchStore(storeBuilder.buildBranchStore()) - .setBlockChainManager(blockChainManager) - .setFactory(ValidatorService.factory()) - .build(); + BlockChain blockChain = BranchConfiguration.getBlockChain(genesis, storeBuilder, policyLoader, + branchId, systemProperties); validatorServiceList.add(new ValidatorService(validatorConfig, blockChain)); } catch (Exception e) { diff --git a/yggdrash-node/src/main/resources/application.yml b/yggdrash-node/src/main/resources/application.yml index 12ffb23dd..ddac6dcad 100644 --- a/yggdrash-node/src/main/resources/application.yml +++ b/yggdrash-node/src/main/resources/application.yml @@ -43,7 +43,7 @@ spring.profiles: dev yggdrash: node: seed-peer-list: - - "ynode://57c6510966903044581c148bb67eb47dbbeebef1@172.16.10.159:32918" + - "ynode://75faa5936ee14d7a13bcd0c36873250a0033cee0@172.16.10.159:32918" --- @@ -52,7 +52,7 @@ spring.profiles: prod yggdrash: node: seed-peer-list: - - "ynode://d2a5721e80dc439385f3abc5aab0ac4ed2b1cd95@52.79.188.79:32918" + - "ynode://8dfce06ba4681d70fa0adf9a39d1f1092e62ae46@54.180.186.118:32918" --- @@ -64,7 +64,6 @@ yggdrash: seed: true chain.enabled: false - --- spring.profiles: master @@ -73,16 +72,6 @@ yggdrash: node: chain.gen: true - ---- - -spring.profiles: proxy - -yggdrash: - node: - validator-list: - - "ynode://77283a04b3410fe21ba5ed04c7bd3ba89e70b78c@127.0.0.1:32901" - --- spring.profiles: validator @@ -90,4 +79,3 @@ spring.profiles: validator yggdrash: node: validator: true - diff --git a/yggdrash-node/src/test/java/io/yggdrash/node/api/BranchDtoTest.java b/yggdrash-node/src/test/java/io/yggdrash/node/api/BranchDtoTest.java index 6c0607891..00af53812 100644 --- a/yggdrash-node/src/test/java/io/yggdrash/node/api/BranchDtoTest.java +++ b/yggdrash-node/src/test/java/io/yggdrash/node/api/BranchDtoTest.java @@ -34,7 +34,7 @@ public class BranchDtoTest { @Test public void convertBranchDto() throws IOException { - String genesisString = FileUtil.readFileToString(TestConstants.BRANCH_FILE, FileUtil.DEFAULT_CHARSET); + String genesisString = FileUtil.readFileToString(TestConstants.branchFile, FileUtil.DEFAULT_CHARSET); JsonObject branch = JsonUtil.parseJsonObject(genesisString); BranchDto dto = BranchDto.of(branch); diff --git a/yggdrash-validator/src/main/java/io/yggdrash/validator/data/pbft/PbftBlockChain.java b/yggdrash-validator/src/main/java/io/yggdrash/validator/data/pbft/PbftBlockChain.java index a88da7536..d10fd44a7 100644 --- a/yggdrash-validator/src/main/java/io/yggdrash/validator/data/pbft/PbftBlockChain.java +++ b/yggdrash-validator/src/main/java/io/yggdrash/validator/data/pbft/PbftBlockChain.java @@ -21,7 +21,6 @@ import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; public class PbftBlockChain implements ConsensusBlockChain { @@ -35,8 +34,6 @@ public class PbftBlockChain implements ConsensusBlockChain getUnConfirmedData() { @Override public ConsensusBlock addBlock(ConsensusBlock block) { - this.lock.lock(); - try { - blockChainManagerMock.addBlock(block); // todo: check efficiency & change index - this.blockKeyStore.put(block.getIndex(), block.getHash().getBytes()); - loggingBlock((PbftBlock) block); - } finally { - this.lock.unlock(); - } + blockChainManagerMock.addBlock(block); // todo: check efficiency & change index + this.blockKeyStore.put(block.getIndex(), block.getHash().getBytes()); + loggingBlock((PbftBlock) block); return block; } diff --git a/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftServerStub.java b/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftServerStub.java index 5a799a689..9899e7952 100644 --- a/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftServerStub.java +++ b/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftServerStub.java @@ -106,7 +106,7 @@ public void broadcastPbftBlock(PbftProto.PbftBlock request, = this.blockChain.getBlockChainManager().getLastConfirmedBlock(); if (lastPbftBlock.getIndex() == newPbftBlock.getIndex() - 1 && lastPbftBlock.getHash().equals(newPbftBlock.getPrevBlockHash())) { - this.blockChain.addBlock(newPbftBlock); + this.pbftService.confirmedBlock(newPbftBlock); } pbftService.getLock().unlock(); } finally { diff --git a/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftService.java b/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftService.java index 80ad51de7..5dff9d941 100644 --- a/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftService.java +++ b/yggdrash-validator/src/main/java/io/yggdrash/validator/service/pbft/PbftService.java @@ -169,9 +169,6 @@ private void mainScheduler() { lock.lock(); PbftBlock block = confirmFinalBlock(); - if (block != null) { - resetUnConfirmedBlock(block.getIndex()); - } lock.unlock(); if (block != null) { broadcastBlock(block, this.proxyNodeMap); @@ -319,26 +316,31 @@ private PbftMessage makePrePrepareMsg() { private long getCurrentViewNumber(long seqNumber) { Map viewChangeMsgMap = getMsgMap(seqNumber, "VIEWCHAN"); + if (viewChangeMsgMap.size() < consensusCount) { + return seqNumber; + } - if (viewChangeMsgMap.size() >= consensusCount) { - - for (int i = 0; i < viewChangeMsgMap.size(); i++) { + long newViewNumber = this.viewNumber; + for (int i = 0; i < viewChangeMsgMap.size(); i++) { + if (((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber() <= this.viewNumber) { + continue; + } - long count = 0; - for (int j = 0; j < viewChangeMsgMap.size(); j++) { - if (((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber() - == ((PbftMessage) viewChangeMsgMap.values().toArray()[j]) - .getViewNumber()) { - count++; - } - } - if (count >= consensusCount) { - return ((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber(); + long count = 0; + for (int j = 0; j < viewChangeMsgMap.size(); j++) { + if (((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber() + == ((PbftMessage) viewChangeMsgMap.values().toArray()[j]) + .getViewNumber()) { + count++; } } + if (count >= consensusCount + && newViewNumber < ((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber()) { + newViewNumber = ((PbftMessage) viewChangeMsgMap.values().toArray()[i]).getViewNumber(); + } } - return seqNumber; + return newViewNumber; } private long getNextActiveValidatorIndex(long index) { @@ -537,15 +539,13 @@ private PbftBlock confirmFinalBlock() { } private PbftMessage makeViewChangeMsg() { - if (this.failCount < FAIL_COUNT - || this.isViewChanged) { + if (this.failCount < FAIL_COUNT) { return null; } Block block = this.blockChain.getBlockChainManager().getLastConfirmedBlock().getBlock(); log.trace("block" + block.toString()); - long seqNumber = block.getIndex() + 1; - long newViewNumber = getNextActiveValidatorIndex(seqNumber); + long newViewNumber = this.viewNumber + 1; if (newViewNumber < 0) { return null; } @@ -577,8 +577,9 @@ private PbftMessage makeViewChangeMsg() { return viewChangeMsg; } - private void confirmedBlock(PbftBlock block) { + public void confirmedBlock(PbftBlock block) { this.blockChain.addBlock(block); + resetUnConfirmedBlock(block.getIndex()); } private void resetUnConfirmedBlock(long index) { @@ -613,7 +614,12 @@ private Map getMsgMap(long index, String msg) { } private void checkPrimary() { - this.viewNumber = getCurrentViewNumber(this.seqNumber); + long checkViewNumber = getCurrentViewNumber(this.seqNumber); + if (checkViewNumber > this.viewNumber) { + this.viewNumber = checkViewNumber; + this.failCount = 0; + } + int primaryIndex = (int) (this.viewNumber % totalValidatorMap.size()); currentPrimaryAddr = (String) totalValidatorMap.keySet().toArray()[primaryIndex]; @@ -690,7 +696,7 @@ private void blockSyncing(String addr, long index) { PbftBlock pbftBlock; long lastConfirmedBlockIndex = this.blockChain.getBlockChainManager().getLastIndex(); if (client.isRunning()) { - List pbftBlockList = client.getBlockList(lastConfirmedBlockIndex); + List pbftBlockList = client.getBlockList(lastConfirmedBlockIndex + 1); log.debug("node: " + client.getId()); log.debug("index: " + (!pbftBlockList.isEmpty() ? pbftBlockList.get(0).getIndex() : null));