From 75cc8d39bc70afe1eeea5685dd6917eb76dd9bd5 Mon Sep 17 00:00:00 2001 From: CaiHQ Date: Sun, 26 Sep 2021 12:50:12 +0800 Subject: [PATCH] initial commit --- .gitignore | 2 + build.gradle | 34 + src/main/java/org/bdware/sc/ChainOpener.java | 30 + .../java/org/bdware/sc/ContractClient.java | 501 ++++ .../java/org/bdware/sc/ContractManager.java | 2328 +++++++++++++++++ src/main/java/org/bdware/sc/ContractMeta.java | 119 + .../org/bdware/sc/ContractStatusEnum.java | 43 + .../org/bdware/sc/ContractStatusRecorder.java | 423 +++ .../bdware/sc/MasterElectTimeRecorder.java | 15 + src/main/java/org/bdware/sc/MasterStub.java | 15 + .../org/bdware/sc/MultiContractRecorder.java | 47 + .../java/org/bdware/sc/ProjectRecorder.java | 46 + .../bdware/sc/RecoverMechTimeRecorder.java | 44 + .../java/org/bdware/sc/event/EventBroker.java | 479 ++++ .../java/org/bdware/sc/event/EventCenter.java | 161 ++ .../org/bdware/sc/event/EventRecorder.java | 237 ++ .../sc/event/clients/ClientConsumer.java | 23 + .../sc/event/clients/ContractConsumer.java | 119 + .../sc/event/clients/IEventConsumer.java | 11 + .../bdware/sc/event/clients/NodeConsumer.java | 39 + .../org/bdware/sc/handler/ManagerHandler.java | 101 + .../org/bdware/sc/sequencing/Committer.java | 7 + .../bdware/sc/sequencing/PBFTAlgorithm.java | 382 +++ .../org/bdware/sc/sequencing/PBFTMember.java | 43 + .../org/bdware/sc/sequencing/PBFTMessage.java | 99 + .../org/bdware/sc/sequencing/PBFTType.java | 35 + .../java/org/bdware/sc/sequencing/Pair.java | 11 + .../sc/sequencing/SequencingAlgorithm.java | 12 + .../bdware/sc/sequencing/ViewAlgorithm.java | 31 + .../java/org/bdware/sc/units/ByteUtil.java | 53 + .../org/bdware/sc/units/ContractRecord.java | 84 + .../sc/units/ContractUnitController.java | 156 ++ .../bdware/sc/units/ContractUnitMember.java | 18 + .../bdware/sc/units/ContractUnitMessage.java | 109 + .../sc/units/ContractUnitStartRequest.java | 46 + .../org/bdware/sc/units/ContractUnitType.java | 30 + .../bdware/sc/units/MultiContractMeta.java | 219 ++ .../java/org/bdware/sc/units/RecoverFlag.java | 5 + .../java/org/bdware/sc/units/RespCache.java | 53 + .../java/org/bdware/sc/units/ResultCache.java | 7 + .../sc/units/SequencingAlgorithmFactory.java | 8 + .../sc/units/TrustfulExecutorConnection.java | 7 + .../server/trustedmodel/ContractExecutor.java | 8 + .../trustedmodel/ContractUnitStatus.java | 8 + .../bdware/sc/test/ContractManagerTest.java | 10 + .../org/bdware/sc/units/RespCacheTest.java | 78 + 46 files changed, 6336 insertions(+) create mode 100644 build.gradle create mode 100644 src/main/java/org/bdware/sc/ChainOpener.java create mode 100644 src/main/java/org/bdware/sc/ContractClient.java create mode 100644 src/main/java/org/bdware/sc/ContractManager.java create mode 100644 src/main/java/org/bdware/sc/ContractMeta.java create mode 100644 src/main/java/org/bdware/sc/ContractStatusEnum.java create mode 100644 src/main/java/org/bdware/sc/ContractStatusRecorder.java create mode 100644 src/main/java/org/bdware/sc/MasterElectTimeRecorder.java create mode 100644 src/main/java/org/bdware/sc/MasterStub.java create mode 100644 src/main/java/org/bdware/sc/MultiContractRecorder.java create mode 100644 src/main/java/org/bdware/sc/ProjectRecorder.java create mode 100644 src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java create mode 100644 src/main/java/org/bdware/sc/event/EventBroker.java create mode 100644 src/main/java/org/bdware/sc/event/EventCenter.java create mode 100644 src/main/java/org/bdware/sc/event/EventRecorder.java create mode 100644 src/main/java/org/bdware/sc/event/clients/ClientConsumer.java create mode 100644 src/main/java/org/bdware/sc/event/clients/ContractConsumer.java create mode 100644 src/main/java/org/bdware/sc/event/clients/IEventConsumer.java create mode 100644 src/main/java/org/bdware/sc/event/clients/NodeConsumer.java create mode 100644 src/main/java/org/bdware/sc/handler/ManagerHandler.java create mode 100644 src/main/java/org/bdware/sc/sequencing/Committer.java create mode 100644 src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java create mode 100644 src/main/java/org/bdware/sc/sequencing/PBFTMember.java create mode 100644 src/main/java/org/bdware/sc/sequencing/PBFTMessage.java create mode 100644 src/main/java/org/bdware/sc/sequencing/PBFTType.java create mode 100644 src/main/java/org/bdware/sc/sequencing/Pair.java create mode 100644 src/main/java/org/bdware/sc/sequencing/SequencingAlgorithm.java create mode 100644 src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java create mode 100644 src/main/java/org/bdware/sc/units/ByteUtil.java create mode 100644 src/main/java/org/bdware/sc/units/ContractRecord.java create mode 100644 src/main/java/org/bdware/sc/units/ContractUnitController.java create mode 100644 src/main/java/org/bdware/sc/units/ContractUnitMember.java create mode 100644 src/main/java/org/bdware/sc/units/ContractUnitMessage.java create mode 100644 src/main/java/org/bdware/sc/units/ContractUnitStartRequest.java create mode 100644 src/main/java/org/bdware/sc/units/ContractUnitType.java create mode 100644 src/main/java/org/bdware/sc/units/MultiContractMeta.java create mode 100644 src/main/java/org/bdware/sc/units/RecoverFlag.java create mode 100644 src/main/java/org/bdware/sc/units/RespCache.java create mode 100644 src/main/java/org/bdware/sc/units/ResultCache.java create mode 100644 src/main/java/org/bdware/sc/units/SequencingAlgorithmFactory.java create mode 100644 src/main/java/org/bdware/sc/units/TrustfulExecutorConnection.java create mode 100644 src/main/java/org/bdware/server/trustedmodel/ContractExecutor.java create mode 100644 src/main/java/org/bdware/server/trustedmodel/ContractUnitStatus.java create mode 100644 src/test/java/org/bdware/sc/test/ContractManagerTest.java create mode 100644 src/test/java/org/bdware/sc/units/RespCacheTest.java diff --git a/.gitignore b/.gitignore index a1c2a23..6aeb7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/build/ +*/build/* # Compiled class file *.class diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..fc351ec --- /dev/null +++ b/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' +} + +sourceSets { + main { + java { + srcDirs 'src/main/java' + } + resources { + srcDir 'src/main/resources' + } + } + test { + java { + srcDir 'src/test/java' + } + resources { + srcDir 'src/test/resources' + } + } +} + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + implementation project(":common") + + testImplementation 'junit:junit:4.13.2' +} diff --git a/src/main/java/org/bdware/sc/ChainOpener.java b/src/main/java/org/bdware/sc/ChainOpener.java new file mode 100644 index 0000000..c62b6b6 --- /dev/null +++ b/src/main/java/org/bdware/sc/ChainOpener.java @@ -0,0 +1,30 @@ +package org.bdware.sc; + +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.OnHashCallback; + +public interface ChainOpener { + + void writeContractResultToLocalAndLedger( + String result, + ContractClient client, + ContractRequest contractRequest, + OnHashCallback cb, long start, long l); + + void writeToChain( + OnHashCallback cb, + String from, + String to, + String data, + String requestID, + String namedLedger); + + void writeToChainWithContract( + OnHashCallback cb, + String from, + String to, + String data, + String requestID, + String contractID, + String namedLedger); +} diff --git a/src/main/java/org/bdware/sc/ContractClient.java b/src/main/java/org/bdware/sc/ContractClient.java new file mode 100644 index 0000000..debcbba --- /dev/null +++ b/src/main/java/org/bdware/sc/ContractClient.java @@ -0,0 +1,501 @@ +package org.bdware.sc; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.ContractResult.Status; +import org.bdware.sc.bean.Contract; +import org.bdware.sc.bean.ContractExecType; +import org.bdware.sc.bean.FunctionDesp; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.conn.SocketGet; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.KeyValueDBUtil; +import org.bdware.sc.encrypt.HardwareInfo; +import org.bdware.sc.encrypt.HardwareInfo.OSType; +import org.bdware.sc.event.REvent.REventSemantics; +import org.bdware.sc.node.AnnotationNode; +import org.bdware.sc.node.YjsType; +import org.bdware.sc.util.JsonUtil; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.util.*; + +public class ContractClient { + private static final Logger LOGGER = LogManager.getLogger(ContractClient.class); + public static String cmi = ""; + public ContractMeta contractMeta; + public boolean isDebug; + transient SocketGet get; + int port; + String pid; + transient ContractPrinter outputTracer; + transient ContractPrinter errorTracer; + Date timeTravel; // 当前所处时间 + transient Process process; + long times = 0L; + long traffic = 0L; + long memory; + // boolean withInsnLimit; // 是否需要计算gas + ContractStatus contractStatus; + boolean isRunning; + + // public boolean changeDumpPeriod(String period) { + // System.out.println("[ContractClient] changeDumpPeriod " + period); + // String res = get.syncGet("", "changeDumpPeriod",period); + // if(res.equals("success")) + // return true; + // return false; + // } + long lastUpdate = 0; + + ContractClient(Contract c) { + contractMeta = new ContractMeta(); + contractMeta.contract = c; + contractMeta.id = contractMeta.contract.getID(); + // contractMeta will be rewrite later + outputTracer = new ContractPrinter(); + errorTracer = new ContractPrinter(); + contractStatus = ContractStatus.Ready; + memory = 0; + timeTravel = new Date(); + isRunning = false; + } + + // Once the reconnect is called, all cp in the same node will reconnect to + // current ContractManager + // We only consider the. + public ContractClient(int startPort) { + // LOGGER.info("ContractClient 构造器 : "); + contractMeta = new ContractMeta(); + outputTracer = new ContractPrinter(); + errorTracer = new ContractPrinter(); + contractStatus = ContractStatus.Ready; + memory = 0; + timeTravel = new Date(); + isRunning = false; + port = startPort; + // LOGGER.info("ContractClient----构造器----- 端口 " + startPort); + + get = new SocketGet("127.0.0.1", startPort); + // LOGGER.info("ContractClient----构造器----- position---2"); + + String cpCMI = get.syncGet("", "isContractProcess", ""); // CMI + + if (!cmi.equals(cpCMI)) { + LOGGER.warn("contract listened to port " + startPort + " cpCMI = " + cpCMI); + // LOGGER.info("ContractClient----构造器----- position---4"); + return; + } + pid = get.syncGet("", "getPID", ""); + // LOGGER.info("ContractClient----构造器----- position---5"); + initProps(); + + // LOGGER.info("ContractClient----构造器----- position---6"); + // LOGGER.info("ContractClient----构造器----- position---7"); + } + + static boolean isBundlePath(String scriptStr) { + return scriptStr.endsWith(".zip") || scriptStr.endsWith(".ypk"); + } + + public static String getPid(Process process) { + long pid = -1; + Field field; + try { + Class clazz = Class.forName("java.lang.UNIXProcess"); + field = clazz.getDeclaredField("pid"); + field.setAccessible(true); + pid = (Integer) field.get(process); + } catch (Throwable e) { + e.printStackTrace(); + } + return String.valueOf(pid); + } + + public boolean isProcessAlive() { + return get != null && get.isAlive(); + } + + public boolean killProcess() { + process.destroy(); + return true; + } + + private void initProps() { + // LOGGER.info("initProps ---- position-----1"); + contractMeta.name = get.syncGet("", "getContractName", ""); + // LOGGER.info("initProps ---- position-----2"); + String strEvent = get.syncGet("", "getDeclaredEvents", ""); + LOGGER.debug("event: " + strEvent); + contractMeta.declaredEvents = + JsonUtil.fromJson( + strEvent, new TypeToken>() { + }.getType()); + // LOGGER.info("initProps ---- position-----3"); + contractMeta.exportedFunctions = + JsonUtil.fromJson( + get.syncGet("", "getExportedFunctions", ""), + new TypeToken>() { + }.getType()); + contractMeta.dependentContracts = JsonUtil.fromJson( + get.syncGet("", "getDependentContracts", ""), + new TypeToken>() { + }.getType()); + // LOGGER.info("initProps ---- position-----4"); + contractMeta.logDetail = new HashMap<>(); + for (FunctionDesp desp : contractMeta.exportedFunctions) { + StringBuilder str = new StringBuilder(); + for (AnnotationNode anno : desp.annotations) { + if (anno.getType().equals("LogType")) { + for (String logType : anno.getArgs()) { + str.append(logType.replaceAll("\"", "")).append(";"); + } + } + if (anno.getType().equals("LogLocation")) { + for (String logLoc : anno.getArgs()) { + if (logLoc.equals("\"dataware\"") + || logLoc.equals("\"bdledger\"") + || logLoc.equals("\"bdledger:\"")) { + str.append("bdcontract;"); + } else if (logLoc.startsWith("\"bdledger:") && logLoc.length() > 11) { + String tmp = logLoc.substring(1); + String tmp2 = tmp.substring(0, tmp.length() - 1); + str.append(tmp2).append(";"); + } + } + } + } + contractMeta.logDetail.put(desp.functionName, str.toString()); + } + // LOGGER.info("initProps ---- position-----5"); + try { + + String anno = get.syncGet("", "getAnnotations", ""); + contractMeta.annotations = + JsonUtil.fromJson(anno, new TypeToken>() { + }.getType()); + + } catch (Exception e) { + // supoort contract process before version 0.70 + contractMeta.annotations = new ArrayList<>(); + } + // LOGGER.info("initProps ---- position-----6"); + contractMeta.sigRequired = Boolean.parseBoolean(get.syncGet("", "isSigRequired", "")); + // LOGGER.info("initProps ---- position-----7"); + contractMeta.thisPermission = get.syncGet("", "showPermission", ""); + // LOGGER.info("initProps ---- position-----8"); + isRunning = true; + contractMeta.isDebug = Boolean.parseBoolean(get.syncGet("", "getDebug", "")); + // LOGGER.info("initProps ---- position-----9"); + get.syncGet("", "registerMangerPort", ContractManager.cPort.getCMPort() + ""); + contractMeta.contract = + JsonUtil.fromJson(get.syncGet("", "getContract", ""), Contract.class); + contractMeta.id = contractMeta.contract.getID(); + get.setOfflineExceptionHandler( + new SocketGet.OfflineHandler() { + @Override + public void onException(SocketGet socketGet, Exception e) { + if (e.getMessage().contains("Connection refused")) { + contractMeta.status = ContractStatusEnum.HANGED; + ContractManager.instance.statusRecorder.resumeContractProcess( + contractMeta.id); + } + } + }); + loadTimesAndTraffic(); + // LOGGER.info("initProps ---- position-----10"); + // LOGGER.debug("======= registerPort:" + ret + "-->" + ContractManager.startPort); + } + + public String startProcess(PrintStream ps) { + isRunning = false; + port = -1; + LOGGER.info("port=" + port); + + String darg = "-Djava.library.path="; + String classpath; + File jniPath; + + if (ContractManager.yjsPath == null) { + jniPath = new File("./jni/"); + classpath = System.getProperty("java.class.path"); + } else { + classpath = ContractManager.yjsPath; + jniPath = new File(classpath).getParentFile(); + } + + String osJni = ((HardwareInfo.type == OSType.linux) ? "/jni/linux" : "/jni/mac"); + darg += jniPath.getAbsolutePath() + osJni; + if (!new File(classpath).exists()) { + ContractResult r = + new ContractResult( + Status.Exception, new JsonPrimitive("incorrect path: yjs.jar")); + return JsonUtil.toJson(r); + } + if (!new File(jniPath, "libs").exists()) { + ContractResult r = + new ContractResult( + Status.Exception, + new JsonPrimitive("incorrect path: yjs.jar, missing libs")); + return JsonUtil.toJson(r); + } + // ProcessBuilder builder = + // new ProcessBuilder( + // "java", + // "-Dfile.encoding=UTF-8", + // darg, + // "-cp", + // jniPath.getAbsolutePath() + "/libs/*:" + classpath, + // "org.bdware.sc.ContractProcess", + // "-port=" + cPort.getPort()); + int startPort = ContractManager.cPort.getPortAndInc(); + ProcessBuilder builder = + new ProcessBuilder( + "java", + "-Dfile.encoding=UTF-8", + darg, + "-jar", + classpath, + "-port=" + startPort, + "-cmi=" + cmi, // cmi 区分不同CM的cp + (isDebug ? "-debug" : "")); + File directory = new File(""); + LOGGER.debug("[CMD] path: " + directory.getAbsolutePath()); + LOGGER.debug(StringUtils.join(builder.command(), " ")); + + Map map = builder.environment(); + map.put("java.library.path", jniPath.getAbsolutePath() + osJni); + builder.directory(new File("./")); + LOGGER.debug("start process:"); + + try { + process = builder.start(); + + this.pid = getPid(process); + LOGGER.info("[CP PPID ] " + pid); + PrintStream printStream = new PrintStream(process.getOutputStream()); + printStream.println("CP PID:" + pid); + printStream.close(); + Scanner sc = new Scanner(process.getInputStream()); + String status = null; + while (sc.hasNext()) { + status = sc.nextLine(); + LOGGER.info("[CP] " + status); + if (status.contains("mainPort")) { + try { + // Set contractPort to max(mainPort, contractPort) + int portIndex = status.indexOf("mainPort"); + int port = + Integer.parseInt( + status.substring(portIndex + 9, portIndex + 14) + .replaceAll("\\s+", "")); + if (port != startPort) { + ContractManager.cPort.reSetPort(port + 1); + } + } catch (Exception ignored) { + } + break; + } + } + if (status != null) { + status = status.replaceAll(".*mainPort ", ""); + } + assert status != null; + port = Integer.parseInt(status.split(" ")[0]); + ContractManager.cPort.updateDb(port, true); + get = new SocketGet("127.0.0.1", port); + get.syncGet("", "setDBInfo", ContractManager.dbPath); + String tagA = (ps == System.out ? "[Contract_" + port + "_out] " : ""); + String tagB = (ps == System.out ? "[Contract_" + port + "_err] " : ""); + outputTracer.track(process, sc, tagA, ps); + errorTracer.track(process, new Scanner(process.getErrorStream()), tagB, ps); + get.syncGet("", "registerMangerPort", String.valueOf(ContractManager.cPort.getCMPort())); + + if (isBundlePath(contractMeta.contract.getScriptStr())) { + status = + get.syncGet( + "", "setContractBundle", JsonUtil.toJson(contractMeta.contract)); + } else { + status = get.syncGet("", "setContract", JsonUtil.toJson(contractMeta.contract)); + } + LOGGER.debug("port:" + port + " status:" + status); + ContractResult r = JsonUtil.fromJson(status, ContractResult.class); + if (r.status == Status.Success) { + initProps(); + get.syncGet("", "setPID", pid); + } else if (r.status == null) { + r.status = Status.Error; + r.result = new JsonPrimitive(status); + status = JsonUtil.toJson(r); + contractMeta.name = get.syncGet("", "getContractName", ""); + } + return status; + } catch (Exception e) { + e.printStackTrace(); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + e.printStackTrace(new PrintStream(bo)); + ContractResult r = + new ContractResult(Status.Exception, new JsonPrimitive(bo.toString())); + return JsonUtil.toJson(r); + } + } + + public void updateMemory() { + try { + if (System.currentTimeMillis() - lastUpdate < 2000) { + return; + } + lastUpdate = System.currentTimeMillis(); + get.asyncGet( + "", + ".UsedMemory", + "", + new ResultCallback() { + @Override + public void onResult(String str) { + memory = Long.parseLong(str); + } + }); + } catch (Exception e) { + // e.printStackTrace(); + } + } + + public Map getEvents() { + return contractMeta.declaredEvents; + } + + public List getExportedFunctions() { + return contractMeta.exportedFunctions; + } + + public String getIdentifier() { + return get.syncGet("", "getIdentifier", " "); + } + + public void setIdentifier(String alias) { + get.asyncGet("", "setIdentifier", alias, null); + } + + public boolean isUnit() { + return contractMeta.contract.getType() != ContractExecType.Sole; + } + + public String signResult(String result) { + return contractMeta.contract.signResult(result); + } + + public String getPubkey() { + return contractMeta.contract.getPublicKey(); + } + + public String getContractID() { + return contractMeta.contract.getID(); + } + + public String getContractName() { + return contractMeta.name; + } + + public String getContractDOI() { + return contractMeta.contract.getDOI(); + } + + public String getContractKey() { + return contractMeta.contract.getKey(); + } + + public void setContractKey(String key) { + contractMeta.contract.setKey(key); + } + + public ContractExecType getContractType() { + return contractMeta.contract.getType(); + } + + public List getAnnotations() { + return contractMeta.annotations; + } + + public int getContractCopies() { + return contractMeta.contract.getNumOfCopies(); + } + + public String getLogType(String action) { + return contractMeta.logDetail.get(action); + } + + public boolean isDebug() { + return contractMeta.isDebug; + } + + public long getTimes() { + return times; + } + + public long getTraffic() { + return traffic; + } + + public YjsType getYjsType() { + return contractMeta.contract.getYjsType(); + } + + public String getPID() { + return pid; + } + + public String getPort() { + return "" + port; + } + + public String getMemory() { + return "" + memory; + } + + public void loadTimesAndTraffic() { + String tempTime2 = + KeyValueDBUtil.instance.getValue( + CMTables.ContractInfo.toString(), contractMeta.name + "-Times"); + String tempTraffic2 = + KeyValueDBUtil.instance.getValue( + CMTables.ContractInfo.toString(), contractMeta.name + "-Traffic"); + if (tempTime2 != null && !tempTime2.equals("")) { + times = Long.parseLong(tempTime2); + } + if (tempTraffic2 != null && !tempTraffic2.equals("")) { + traffic = Long.parseLong(tempTraffic2); + } + } + + public void saveTimesAndTraffic() { + KeyValueDBUtil.instance.setValue( + CMTables.ContractInfo.toString(), contractMeta.name + "-Times", times + ""); + KeyValueDBUtil.instance.setValue( + CMTables.ContractInfo.toString(), contractMeta.name + "-Traffic", traffic + ""); + } + + public void setMask(JsonObject args) { + + //get.asyncGet("",,,,); + get.asyncGet("", "setMask", args.toString(), null); + + } + + public void setProjectConfig(String args) { + get.asyncGet("", "setProjectConfig", args, null); + } + //public String + + static class ReqScript { + String mode; + String code; + } +} diff --git a/src/main/java/org/bdware/sc/ContractManager.java b/src/main/java/org/bdware/sc/ContractManager.java new file mode 100644 index 0000000..4009654 --- /dev/null +++ b/src/main/java/org/bdware/sc/ContractManager.java @@ -0,0 +1,2328 @@ +package org.bdware.sc; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import io.prometheus.client.Counter; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.doip.core.model.handleRecord.DoHandleRecord; +import org.bdware.doip.core.utils.GlobalConfigurations; +import org.bdware.doip.endpoint.irpClient.GlobalIrpClient; +import org.bdware.sc.ContractResult.Status; +import org.bdware.sc.bean.*; +import org.bdware.sc.conn.OnHashCallback; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.conn.ServiceServer; +import org.bdware.sc.db.*; +import org.bdware.sc.event.EventBroker; +import org.bdware.sc.event.REvent; +import org.bdware.sc.event.REvent.REventSemantics; +import org.bdware.sc.handler.ManagerHandler; +import org.bdware.sc.node.*; +import org.bdware.sc.units.MultiContractMeta; +import org.bdware.sc.units.RespCache; +import org.bdware.sc.util.JsonUtil; +import org.bdware.sc.util.VersionUtil; +import org.hyperic.sigar.Mem; +import org.hyperic.sigar.ProcMem; +import org.hyperic.sigar.Sigar; +import org.zz.gmhelper.SM2KeyPair; +import org.zz.gmhelper.SM2Util; + +import java.io.*; +import java.util.*; +import java.util.concurrent.*; + +public class ContractManager { + public static final Object checkAndStartLock = new Object(); + static final Counter contractCounter = + Counter.build() + .name("execute_contract_counter") + .help("contract execution status") + .register(); + static final Counter oracleCounter = + Counter.build() + .name("execute_oracle_counter") + .help("oracle execution status") + .register(); + + static final Counter totalCounter = + Counter.build() + .name("execute_counter") + .help("contract and oracle execution status") + .register(); + + static final long memoryLimit = 100L * 1024L * 1024L; + private static final Logger LOGGER = LogManager.getLogger(ContractManager.class); + public static String yjsPath = null; + public static String dbPath; + public static String dir; + public static ContractManager instance; + public static Boolean reconnectFinish = false; + public static MultiIndexTimeRocksDBUtil logsDB; + public static ContractPort cPort; + public static boolean eventPersistenceEnabled = false; + public static ExecutorService threadPool = + new ThreadPoolExecutor( + 8, + 15, + 60, + TimeUnit.SECONDS, + new SynchronousQueue<>()); + public static ScheduledExecutorService scheduledThreadPool = + Executors.newScheduledThreadPool(10); + + public static DoipServiceInfoConfigurer doipConfigurer; + + public static int logStage = 0; + public static Sigar sigar = null; // 获取network等资源什么 + + static { + KeyValueDBUtil.setupCM(); + KeyValueRocksDBUtil.setupCM(); + TimeDBUtil.setupCM(); + logsDB = + new MultiIndexTimeRocksDBUtil( + "./ContractManagerDB", CMTables.LocalContractLogDB.toString()); + } + + protected final EventBroker eventBroker; + public NodeCenterConn nodeCenterConn; + public MasterStub masterStub; + public ChainOpener chainOpener; + public ContractStatusRecorder statusRecorder; + public MultiContractRecorder multiContractRecorder; + public ProjectRecorder projectRecoder; + ManagerHandler handler; + ServiceServer server; + Map reqCache = new ConcurrentHashMap<>(); // key is requestID + private ContractClient analysisClient; + // private long expiredTime; + + public ContractManager() { + instance = this; + handler = new ManagerHandler(this); + int startPort = cPort.getPortAndInc(); + server = new ServiceServer(handler, startPort); + cPort.setCMPort(server.getPort()); + if (server.getPort() != startPort) { + cPort.reSetPort(server.getPort() + 1); + } + eventBroker = new EventBroker(); + statusRecorder = new ContractStatusRecorder("./ContractManagerDB"); + multiContractRecorder = new MultiContractRecorder("./ContractManagerDB"); + projectRecoder = new ProjectRecorder("./ContractManagerDB"); + } + + private static String convertToBytes(long traffic) { + String[] unit = new String[]{"B", "KB", "MB", "GB", "TB"}; + double d = traffic; + int i; + for (i = 0; i < unit.length - 1; i++) { + if (d > 1024.0) { + d /= 1024.0; + } else { + break; + } + } + return String.format("%.2f %s", d, unit[i]); + } + + // public static void updateContractHandleRecord(Contract c, ResultCallback resultCallback) + // throws Exception { + // + // DoHandleRecord contractHR = new DoHandleRecord(GlobalConfigurations.User_Handle,) + // resultCallback.onResult(contractDOI); + // } + + // public static void updateContractHandleRecord(Contract c, String doi, ResultCallback + // resultCallback) + // throws Exception { + // // Dictionary contractInfo = new Hashtable(); + // // contractInfo.put("id", c.getID()); + // // contractInfo.put("publicKey", c.getPublicKey()); + // + // HandleService hs = new HandleService(HandleServiceUtils.hrRegister); + // String contractDOI = + // hs.reregisterContract(doi, DOIPMainServer.repoIdentifier, c.getPublicKey()); + // + // DigitalObject contractDO = queryContractDo(c, contractDOI); + // // DOAClient.getGlobalInstance().create(DOIPMainServer.repoIdentifier, contractDO); + // DoipClient doipClient = + // DoipClient.createByRepoUrlAndMsgFmt( + // DOIPMainServer.repoUrl, DoipMessageFormat.PACKET.getName()); + // + // // RepoHandleRecord repoHandleRecord = + // // DOAClient.getGlobalInstance().resolveRepo(DOIPMainServer.repoIdentifier); + // + // DoMessage response = doipClient.update(contractDO); + // if (response.parameters.response != DoResponse.Success) { + // doipClient.create(DOIPMainServer.repoIdentifier, contractDO); + // } + // resultCallback.onResult(contractDOI); + // } + // + // private static DigitalObject queryContractDo(Contract c, String contractDOI) + // throws IOException { + // DigitalObject cDo = new DigitalObject(contractDOI, DoType.Contract_ypk); + // Element e = new Element("contract", "ypk"); + // e.setData( + // toByteArray( + // new ContractInstanceDO( + // c.getID(), c.getPublicKey(), + // fileToByteArray(c.getScriptStr())))); + // cDo.addElements(e); + // return cDo; + // } + // + // public static void registerMultiContractToDOA(Contract c, String doi) throws Exception { + // HandleService hs = new HandleService(HandleServiceUtils.hrRegister); + // hs.reregisterContract(doi, c.getNodeCenterRepoDOI(), c.getPublicKey()); + // + // DigitalObject contractDO = new DigitalObject(doi, DoType.Contract_ypk); + // Element e = new Element("contract", "ypk"); + // e.setData( + // toByteArray( + // new ContractInstanceDO( + // c.getID(), c.getPublicKey(), + // fileToByteArray(c.getScriptStr())))); + // contractDO.addElements(e); + // DoMessage response = + // DOAClient.getGlobalInstance().create(c.getNodeCenterRepoDOI(), contractDO); + // + // if (response.parameters.response != DoResponse.Success) { + // DOAClient.getGlobalInstance().update(contractDO); + // } + // /* + // DoipClient doipClient = DoipClient.createByRepoUrl(DOIPMainServer.repoUrl); + // DoMessage response = doipClient.create(contractDO); + // if (response.header.response != DoResponse.Success) { + // doipClient.update(contractDO); + // } + // */ + // } + + public static byte[] fileToByteArray(String filename) throws IOException { + File f = new File(filename); + if (!f.exists()) { + throw new FileNotFoundException(filename); + } + + try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length())) { + BufferedInputStream in; + in = new BufferedInputStream(new FileInputStream(f)); + int buf_size = 1024; + byte[] buffer = new byte[buf_size]; + int len; + while (-1 != (len = in.read(buffer, 0, buf_size))) { + bos.write(buffer, 0, len); + } + return bos.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } + } + + public static byte[] toByteArray(Object obj) { + byte[] bytes = null; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(obj); + oos.flush(); + bytes = bos.toByteArray(); + oos.close(); + bos.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + return bytes; + } + + public static Object toObject(byte[] bytes) { + Object obj = null; + try { + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis); + obj = ois.readObject(); + ois.close(); + bis.close(); + } catch (IOException | ClassNotFoundException ex) { + ex.printStackTrace(); + } + return obj; + } + + // ps是什么 + public static Map> getContainerResourceInfoByPs() { + try { + Map> contractResourceInfo = new HashMap<>(); + List commands = new ArrayList<>(); + commands.add("sh"); + commands.add("-c"); + commands.add("ps -aux | grep ContractProcess | grep java"); + ProcessBuilder builder = new ProcessBuilder(commands); + Process process = builder.start(); + Scanner sc = new Scanner(process.getInputStream()); + String line; + while (sc.hasNext()) { + if ((line = sc.nextLine()).contains("aux")) { + continue; + } + LOGGER.debug("line: " + line); + + String pid = null; + String cpu = null; + String mem = null; + String vsz = null; + String rss = null; + + Scanner sc2 = new Scanner(new ByteArrayInputStream(line.getBytes())); + if (sc2.hasNext()) { + sc2.next(); + if (sc2.hasNextInt()) { + pid = String.valueOf(sc2.nextInt()); + } + if (sc2.hasNextFloat()) { + cpu = sc2.nextFloat() + "%"; + } + if (sc2.hasNextFloat()) { + mem = sc2.nextFloat() + "%"; + } + if (sc2.hasNextInt()) { + vsz = sc2.nextInt() + " Bytes"; + } + if (sc2.hasNextInt()) { + rss = sc2.nextInt() + " Bytes"; + } + } + fillContractResourceInfo(contractResourceInfo, pid, rss, cpu, mem, sc2); + } + return contractResourceInfo; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + // By ps 和 by top + public static Map> getContainerResourceInfoByTop() { + try { + Map> contractResourceInfo = new HashMap<>(); + + Process process = Runtime.getRuntime().exec("top -b -n 1"); + InputStreamReader ir = new InputStreamReader(process.getInputStream()); + LineNumberReader input = new LineNumberReader(ir); + String line; + while ((line = input.readLine()) != null) { + if (!line.contains("java")) { + continue; + } + // System.out.println(line); + + String pid = null; + String res = null; + String cpu = null; + String mem = null; + + Scanner sc2 = new Scanner(new ByteArrayInputStream(line.getBytes())); + if (sc2.hasNext()) { + if (sc2.hasNextInt()) { + pid = String.valueOf(sc2.nextInt()); + } + sc2.next(); + sc2.next(); + sc2.next(); + sc2.next(); + if (sc2.hasNextInt()) { + res = sc2.nextInt() + " Bytes"; + } + sc2.next(); + sc2.next(); + if (sc2.hasNextFloat()) { + cpu = sc2.nextFloat() + "%"; + } + if (sc2.hasNextFloat()) { + mem = sc2.nextFloat() + "%"; + } + } + fillContractResourceInfo(contractResourceInfo, pid, res, cpu, mem, sc2); + } + return contractResourceInfo; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private static void fillContractResourceInfo( + Map> contractResourceInfo, + String pid, + String res, + String cpu, + String mem, + Scanner sc2) { + sc2.close(); + + Map tmp = new HashMap<>(); + tmp.put("%CPU", cpu); + tmp.put("%MEM", mem); + tmp.put("MEM", res); + contractResourceInfo.put(pid, tmp); + } + + public static boolean checkNodeManager(String owner) { + String nodeManagerPubkey = + KeyValueDBUtil.instance.getValue(CMTables.ConfigDB.toString(), "__NodeManager__"); + boolean isNodeManager = false; + if (null != nodeManagerPubkey && nodeManagerPubkey.length() > 0) { + isNodeManager = (nodeManagerPubkey.equals(owner)); + } + return isNodeManager; + } + + public static void checkPointToLedger( + OnHashCallback cb, String contractID, String data, String requestID) { + boolean isMaster = instance.getContractIsMaster(contractID); + if (!isMaster) { + LOGGER.info("非合约 " + contractID + "的master,无需承担checkPoint上链任务!"); + return; + } + instance.chainOpener.writeToChain(cb, contractID, "checkPoint", data, requestID, ""); + } + + public static String getContractMd5(String scriptOrPath, Contract c) { + try { + if (c.getScriptStr().startsWith("/")) { + String md5 = DigestUtils.md5Hex(new FileInputStream(scriptOrPath)); + LOGGER.debug("starting contract: ypk.md5=" + md5); + if (c.getOwner() != null) { + md5 = DigestUtils.md5Hex(md5 + c.getOwner()); + } + LOGGER.debug("starting contract: ypk.md5=" + md5 + " owner.md5=" + c.getOwner()); + return "bundle_" + md5; + } else { + String md5 = DigestUtils.md5Hex(c.getScriptStr()); + if (c.getOwner() != null) md5 = DigestUtils.md5Hex(md5 + c.getOwner()); + return "script_" + md5; + } + } catch (Exception e) { + e.printStackTrace(); + return "tempr_" + new Random().nextLong(); + } + } + + public void initAnalysisClient() { + Contract c = new Contract(); + c.setType(ContractExecType.Sole); + SM2KeyPair pair = SM2Util.generateSM2KeyPair(); + c.setOwner(pair.getPublicKeyStr()); + c.setScript("contract analysis_client{}"); + analysisClient = new ContractClient(c); + analysisClient.startProcess(System.out); + } + + public String getContractIDByName(String name) { + ContractMeta meta = statusRecorder.getContractMeta(name); + if (meta != null) return meta.id; + return null; + } + + public String getContractNameByID(String id) { + ContractMeta meta = statusRecorder.getContractMeta(id); + if (meta != null) return meta.name; + return null; + } + + // public void setExpiredTime(long time) { + // expiredTime = time; + // } + + public void reconnectContractProcess() { + RecoverMechTimeRecorder.startReconnectCP = System.currentTimeMillis(); + LOGGER.info("reconnectContractProcess"); + cPort.visitReconnectPortRange(statusRecorder.getVisitor()); + statusRecorder.reSyncStatusAtStart(); + synchronized (checkAndStartLock) { + reconnectFinish = true; + LOGGER.info("checkAndStartLock is set to true"); + checkAndStartLock.notifyAll(); + RecoverMechTimeRecorder.reconnectCPFinish = System.currentTimeMillis(); + } + } + + public ContractClient getContractClientByDoi(String doi) { + ContractMeta meta = statusRecorder.getContractMeta(doi); + return statusRecorder.getContractClient(meta.id); + } + + public String startContractAndRedirectWithDebug(Contract c) { + return startContractAndRedirect(c, System.out, null, true); + } + + public String startContractAndRedirect(Contract c, PrintStream ps) { + return startContractAndRedirect(c, ps, null, false); + } + + public String getContractStateful(String contractID) { + ContractClient cc = getClient(contractID); + if (null == cc) { + return "true"; + } + return cc.get.syncGet("", "getStateful", ""); + } + + public String getDumpPeriod(String contractName) { + ContractClient client = getByName(contractName); + if (client == null) { + return "failed"; + } + String result = client.get.syncGet("", "getDumpPeriod", "a"); + ContractMeta meta = client.contractMeta; + addLocalContractLog( + "getDumpPeriod", meta.contract.getID(), meta.name, meta.contract.getOwner()); + + if (!"failed".equals(result)) { + return result; + } + return "failed"; + } + + // load latest memory after startContract + public void initLoadMemory(ContractClient client) { + + if (client == null) { + LOGGER.debug("contract process not found"); + return; + } + + // init annotation + String ff = client.get.syncGet("", "getMemorySet", ""); + LOGGER.debug("[init load] memorySet : " + ff); + if (null == ff || ff.isEmpty()) { + LOGGER.debug("do not need load when start, finished"); + return; + } + // String[] strs = ff.split(";"); + + if (!ff.contains("init")) { + LOGGER.debug("do not need load when start,finished"); + return; + } + String path = findNewestMemory(client.getContractName()); + if (path != null) { + String result = loadMemory(client, path); + if (result.equals("success")) { + LOGGER.debug("init load memory finished"); + } else { + LOGGER.debug("init load memory failed"); + } + } else { + LOGGER.debug("no memory file,init load memory failed"); + } + } + + public String findNewestMemory(String contractName) { + File f = new File(dir + "/memory/" + contractName); + if (f.exists() && f.isDirectory()) { + String path = null; + for (File subFile : Objects.requireNonNull(f.listFiles())) { + if (path == null) path = subFile.getName(); + else if (path.compareTo(subFile.getName()) < 0) { + path = subFile.getName(); + } + } + if (path == null) { + LOGGER.debug("no memory file,init load memory finished"); + return null; + } + path = f.getAbsolutePath() + "/" + path; + return path; + } + return null; + } + + public String changeDumpPeriod(String contractName, String dumpPeriod) { + ContractResult r; + ContractClient client = getByName(contractName); + ProjectConfig config = projectRecoder.getProjectConfig(contractName); + config.setDumpPeriod(dumpPeriod); + if (null == client) { + r = new ContractResult(Status.Error, new JsonPrimitive("contract process not found")); + return JsonUtil.toJson(r); + } + loadProjectConfig(client); + + ContractMeta meta = client.contractMeta; + addLocalContractLog( + "changeDumpPeriod", meta.contract.getID(), meta.name, meta.contract.getOwner()); + r = new ContractResult(Status.Success, new JsonPrimitive("change dump period finished")); + + return JsonUtil.toJson(r); + } + + // public String resumeStartContractAndRedirect(Contract c, PrintStream ps, String alias) { + // long freeMemory = getFreeMemory(); + // if (freeMemory < memoryLimit) { + // ContractResult r = + // new ContractResult( + // Status.Error, new JsonPrimitive("insufficient memory:" + + // freeMemory)); + // return JsonUtil.toJson(r); + // } + // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + // Date date = new Date(); + // try { + // date = format.parse("2022-03-31"); + // } catch (ParseException e) { + // e.printStackTrace(); + // } + // if (System.currentTimeMillis() > date.getTime()) { + // ContractResult ret = + // new ContractResult(Status.Error, new JsonPrimitive("Licence Time + // Expired")); + // return JsonUtil.toJson(ret); + // } + // SM2KeyPair sm2Key = SM2Util.generateSM2KeyPair(null); + // ContractResult r; + // try { + // if (c.getScriptStr().startsWith("/")) { + // c.startInfo.isYPK = true; + // String[] str = c.getScriptStr().split("/"); + // String tmp = new File(dir + "/publicCompiled/ypkName").getAbsolutePath(); + // String[] str2 = tmp.split("/"); + // if (str.length > str2.length) { + // c.startInfo.isPrivate = true; + // c.startInfo.pubKeyPath = str[str.length - 2]; + // } + // c.startInfo.ypkName = str[str.length - 1]; + // LOGGER.info("print Contract Start Info"); + // c.startInfo.print(); + // } + // if (null == c.getKey()) { + // String key = getContractMd5(c); + // String privateKeyStr = + // KeyValueDBUtil.instance.getValue(CMTables.ContractInfo.toString(), + // key); + // if (privateKeyStr == null || privateKeyStr.length() == 0) { + // privateKeyStr = SM2Util.generateSM2KeyPair().toJson(); + // KeyValueDBUtil.instance.setValue( + // CMTables.ContractInfo.toString(), key, privateKeyStr); + // } + // sm2Key = SM2KeyPair.fromJson(privateKeyStr); + // c.setKey(sm2Key.getPrivateKey().toString(16)); + // c.setPublicKey(sm2Key.getPublicKeyStr()); + // } + // int contractID; + // if (null != c.getHash() && !c.getHash().equals("")) { + // contractID = Integer.parseInt(c.getHash()); + // } else { + // contractID = c.getPublicKey().hashCode(); + // } + // + // if (c.getID() == null) c.setID(contractID)); + // if (null == c.getOwner()) { + // c.setOwner(sm2Key.getPublicKeyStr()); + // } + // LOGGER.debug("contract pubKey: " + c.getPublicKey()); + // // 合约启动时读取Manifest文件设置合约DOI + // setContractDOI(c); + // setContractStateful(c); + // ContractClient client = new ContractClient(c); + // String ret; + // String conflictCheck; + // addLocalContractLog("startContract", c.getID(), client.contractName, + // c.getOwner()); + // switch (c.getType()) { + // case RequestOnce: + // case ResponseOnce: + // LOGGER.debug("[Start Unsync Contract]"); + // ret = client.startProcess(ps); + // conflictCheck = resumeCheckConflict(c, client, ret); + // if (null != conflictCheck) { + // return conflictCheck; + // } + // + // String tempTime2 = + // KeyValueDBUtil.instance.getValue( + // CMTables.ContractInfo.toString(), + // client.contractName + "-Times"); + // String tempTraffic2 = + // KeyValueDBUtil.instance.getValue( + // CMTables.ContractInfo.toString(), + // client.contractName + "-Traffic"); + // if (tempTime2 != null && !tempTime2.equals("")) { + // client.times = Long.parseLong(tempTime2); + // } + // if (tempTraffic2 != null && !tempTraffic2.equals("")) { + // client.traffic = Long.parseLong(tempTraffic2); + // } + // client.traffic += ret.length(); + // + // client.get.syncGet("", "setDir", ContractManager.dir); + // initLoadMemory(c.getID()); + // ContractStatusRecorder.createContract(c, client); + // + // return ret; + // case RequestAllResponseAll: + // case RequestAllResponseFirst: + // case RequestAllResponseHalf: + // case Sole: + // ret = client.startProcess(ps); + // conflictCheck = checkConflict(c, client, ret); + // if (null != conflictCheck) { + // return conflictCheck; + // } + // + // String tempTime = + // KeyValueDBUtil.instance.getValue( + // CMTables.ContractInfo.toString(), + // client.contractName + "-Times"); + // String tempTraffic = + // KeyValueDBUtil.instance.getValue( + // CMTables.ContractInfo.toString(), + // client.contractName + "-Traffic"); + // if (tempTime != null && !tempTime.equals("")) { + // client.times = Long.parseLong(tempTime); + // } + // if (tempTraffic != null && !tempTraffic.equals("")) { + // client.traffic = Long.parseLong(tempTraffic); + // } + // client.traffic += ret.length(); + // + // if (alias != null) { + // client.setIdentifier(alias); + // } + // if (c.getDoipFlag() && !c.getDOI().equals("registerFailed")) { + // client.setIdentifier(c.getDOI()); + // } + // client.get.syncGet("", "setDir", ContractManager.dir); + // initLoadMemory(c.getID()); + // ContractStatusRecorder.updateContract(c, client); + // return ret; + // default: + // return "todo"; + // } + // } catch (Exception e) { + // e.printStackTrace(); + // r = new ContractResult(Status.Error, new JsonPrimitive("exception occurs")); + // return JsonUtil.toJson(r); + // } + // } + // + // private String resumeCheckConflict(Contract c, ContractClient client, String ret) { + // if (JsonUtil.fromJson(ret, ContractResult.class).status == Status.Success) { + // if (findConflictOfName(client.getContractName())) { + // ContractManager.instance.invokeContractSuicide(client); + // ContractResult r = + // new ContractResult( + // Status.Error, new JsonPrimitive("Duplicate contract name.")); + // return JsonUtil.toJson(r); + // } + // ContractStatusRecorder.updateContractClient(c.getID(), client); + // } + // return null; + // } + // + // public String resumeStartContract(Contract c) { + // return resumeStartContractAndRedirect(c, System.out, null); + // } + + public long getFreeMemory() { + try { + if (null == sigar) { + sigar = new Sigar(); + } + Mem mem = sigar.getMem(); + LOGGER.debug("[free memory] " + mem.getFree() + " " + mem.getActualFree()); + return mem.getFree(); + } catch (Throwable e) { + e.printStackTrace(System.err); + e.printStackTrace(); + } + return memoryLimit + 1; + } + + // private void hangUpKillContract(String contractID) { + // ContractClient client = ContractStatusRecorder.getContractClientById(contractID); + // if (client != null) { + // try { + // String ff = client.get.syncGet("", "getMemorySet", ""); + // LOGGER.info("[killContract] memorySet : " + ff); + // if (ff != null) { + // if (ff.contains("kill")) { + // LOGGER.info("need dump when stop"); + // String contractName = client.getContractName(); + // SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd.HH:mm:ss"); + // File f = new File(dir + "/memory/" + contractName, df.format(new + // Date())); + // dumpContract(contractName, f.getAbsolutePath()); + // } + // } + // String contractName = client.contractName; + // ContractStatusRecorder.id2ContractClient.remove(contractID); + // name2Contract.remove(contractName); + // doi2Contract.remove(client.getContractDOI()); + // String str = client.getIdentifier(); + // if (str != null && !str.equals("null")) { + // name2Contract.remove(str, client); + // } + // ContractManager.instance.invokeContractSuicide(client); + // eventBroker.unSub(contractName, null); + // + // LOGGER.info( + // String.format( + // "killContract: %s id--> %s result: %s", + // client.contractName, contractID, r)); + // } catch (Exception e) { + // e.printStackTrace(); + // } + // } + // } + + public void addLocalContractLog(String action, ContractClient client) { + ContractMeta meta = client.contractMeta; + addLocalContractLog(action, meta.contract.getID(), meta.name, meta.contract.getOwner()); + } + + public String startContractAndRedirect(Contract c, PrintStream ps, String alias, boolean isDebug) { + long freeMemory = getFreeMemory(); + if (freeMemory < memoryLimit || statusRecorder.runningProcess.size() > 8) { + statusRecorder.hangLeastUsedContractProcess(); + // ContractResult r = + // new ContractResult( + // Status.Error, new JsonPrimitive("insufficient memory:" + freeMemory)); + // return JsonUtil.toJson(r); + } + // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + ContractResult r; + try { + if (c.getScriptStr().startsWith("/")) { + c.startInfo.isYPK = true; + String[] str = c.getScriptStr().split("/"); + String tmp = new File(dir + "/publicCompiled/ypkName").getAbsolutePath(); + String[] str2 = tmp.split("/"); + if (str.length > str2.length) { + c.startInfo.isPrivate = true; + c.startInfo.pubKeyPath = str[str.length - 2]; + } + c.startInfo.ypkName = str[str.length - 1]; + LOGGER.info(c.startInfo); + } + allocateKeyIfNotExists(c); + if (null == c.getOwner()) { + c.setOwner(c.getPublicKey()); + } + + LOGGER.debug("contract pubKey: " + c.getPublicKey()); + + // 合约启动时读取Manifest文件设置合约DOI + setContractDOI(c); + + setContractStateful(c); + + // if (c.getDoipFlag()) { + // // 合约部署时,更新合约HandleRecord + // long tmpStart = System.currentTimeMillis(); + // try { + // if (c.getDOI() == null || c.getDOI().equals("")) { + // updateContractHandleRecord( // 创建DO + // c, + // new ResultCallback() { + // @Override + // public void onResult(String str) { + // c.setDOI(str); + // // c.setID(str); + // LOGGER.debug("contract DOI: " + c.getDOI()); + // } + // }); + // } else { + // if (findConflictOfDOI(c.getDOI())) { + // r = new ContractResult(Status.Error, "Duplicate contract + // DOI."); + // return JsonUtil.toJson(r); + // } + // } + // } catch (Exception e) { + // ByteArrayOutputStream bo = new ByteArrayOutputStream(); + // e.printStackTrace(new PrintStream(bo)); + // c.setDOI("registerFailed"); + // } + // LOGGER.info("DOIP executeTime: " + (System.currentTimeMillis() - + // tmpStart)); + // } else c.setDOI("registerDisabled"); + + // if (contracts.containsKey(c.getID())) { + // r = new ContractResult(Status.Error, "contract existed"); + // return JsonUtil.toJson(r); + // } + + ContractClient client = new ContractClient(c); + client.isDebug = isDebug; + String ret; + String conflictCheck; + addLocalContractLog("startContract", c.getID(), client.contractMeta.name, c.getOwner()); + switch (c.getType()) { + case RequestOnce: + case ResponseOnce: + LOGGER.debug("[Start Async Contract]"); + ret = client.startProcess(ps); + conflictCheck = checkConflict(c, client, ret); + if (null != conflictCheck) { + return conflictCheck; + } + client.traffic += ret.length(); + // TODO @ZYX + client.get.syncGet("", "setDir", ContractManager.dir); + initLoadMemory(client); + statusRecorder.createContract(client); + loadProjectConfig(client); + return ret; + case RequestAllResponseAll: + case RequestAllResponseFirst: + case RequestAllResponseHalf: + case Shading: + case Sole: + ret = client.startProcess(ps); + conflictCheck = checkConflict(c, client, ret); + if (null != conflictCheck) { + return conflictCheck; + } + + client.traffic += ret.length(); + // TODO @ZYX + // updateContractInfo(client); + + if (alias != null) { + client.setIdentifier(alias); + } + if (c.getDoipFlag() && !c.getDOI().equals("registerFailed")) { + client.setIdentifier(c.getDOI()); + } + + // if (BDOAHandler.cm != null) { + // BDOAHandler.cm.name2Contract = name2Contract; + // } + + client.get.syncGet("", "setDir", ContractManager.dir); + // Signature sig = sm2.sign(ret.getBytes(),sm2Key.getPrivateKey()); + // ContractResult result = JsonUtil.fromJson(ret, ContractResult.class); + // result.pubkey = sm2Key.getPublicKeyStr(); + // result.result = + // sm2.sign(result.result.getBytes(),sm2Key.getPrivateKey()).toString(); + // return JsonUtil.toJson(result); + + initLoadMemory(client); + statusRecorder.createContract(client); + loadProjectConfig(client); + + return ret; + default: + return "todo"; + } + } catch (Exception e) { + e.printStackTrace(); + r = new ContractResult(Status.Error, new JsonPrimitive("exception occurs")); + return JsonUtil.toJson(r); + } + } + + public void loadProjectConfig(ContractClient client) { + // a + // from DB + ProjectConfig config = projectRecoder.getProjectConfig(client.getContractID()); + LOGGER.debug("zz"); + String str = client.contractMeta.contract.getScriptStr(); + /*if(config!=null){ + System.out.println("not NULL"); + client.setProjectConfig(JsonUtil.toJson(config)); + return; + }*/ + // TODO + loadMaskConfig(config, str); + loadMockConfig(config, str); + client.setProjectConfig(JsonUtil.toJson(config)); + } + + private void loadMockConfig(ProjectConfig config, String str) { + MockConfig mockDB = config.getMockConfig(); + MockConfig mockYPK = YJSPacker.getConfig(str, "/mockConfig.json", MockConfig.class); + if (mockYPK != null) { + if (mockDB == null) { + config.setMockConfig(mockYPK); + } else { + int mockVersion = VersionUtil.compareVersion(mockDB.version, mockYPK.version); + if (mockVersion == -1) { + // YPK + config.setMockConfig(mockYPK); + // client.setProjectConfig(JsonUtil.toJson(config)); + } //else { + // bubian + //} + } + } + + } + + private void loadMaskConfig(ProjectConfig config, String str) { + //从ypk当中检查是否更新,若YPK中没有配置文件则不更新 + //若有且版本号更大,则更新 + MaskConfig maskYPK = YJSPacker.getConfig(str, "/maskConfig.json", MaskConfig.class); + if (maskYPK != null) { + //System.out.println("YPKConf" + maskYPK.config); + MaskConfig maskDB = config.getMaskConfig(); + if (maskDB == null) { + config.setMaskConfig(maskYPK); + //System.out.println("maskNull" + config.getMaskConfig().config); + } else { + int maskVersion = VersionUtil.compareVersion(maskDB.version, maskYPK.version); + if (maskVersion == -1) { + // YPK版本更新 + config.setMaskConfig(maskYPK); + //System.out.println("mask-1" + config.getMaskConfig().config); + // client.setProjectConfig(JsonUtil.toJson(config)); + } + + } + } + + } + + public void allocateKeyIfNotExists(Contract c) { + if (null == c.getKey()) { + String key = getContractMd5(c.getScriptStr(), c); + String privateKeyStr = + KeyValueDBUtil.instance.getValue(CMTables.ContractInfo.toString(), key); + if (privateKeyStr == null || privateKeyStr.length() == 0) { + privateKeyStr = SM2Util.generateSM2KeyPair().toJson(); + KeyValueDBUtil.instance.setValue( + CMTables.ContractInfo.toString(), key, privateKeyStr); + } + SM2KeyPair sm2Key = SM2KeyPair.fromJson(privateKeyStr); + c.setKey(sm2Key.getPrivateKey().toString(16)); + c.setPublicKey(sm2Key.getPublicKeyStr()); + c.setID(String.valueOf(c.getPublicKey().hashCode())); + } + } + + private String checkConflict(Contract c, ContractClient client, String ret) { + if (JsonUtil.fromJson(ret, ContractResult.class).status == Status.Success) { + if (findConflictOfName(client.getContractName())) { + invokeContractSuicide(client); + ContractResult r = + new ContractResult( + Status.Error, new JsonPrimitive("Duplicate contract name.")); + return JsonUtil.toJson(r); + } + } + return null; + } + + private void setContractDOI(Contract c) { + if (DoConfig.callContractUsingDOI) { + ContractManifest cm = YJSPacker.getManifest(c.getScriptStr()); + if (cm != null && cm.doi != null) { + if (!cm.doi.equals("") + && !cm.doi.equals("null") + && c.getType() == ContractExecType.Sole) { + c.setDOI(cm.doi); + + c.setBuildTime(cm.buildTime); + c.setDoipFlag(true); + + DoHandleRecord dohr = + new DoHandleRecord( + GlobalConfigurations.User_Handle, + GlobalConfigurations.DoipServiceID); + dohr.handle = cm.doi; + + threadPool.execute( + () -> { + try { + GlobalIrpClient.getGlobalClient().reRegister(dohr); + } catch (Exception e) { + LOGGER.warn("unable to connect LHS: " + e.getMessage()); + } + }); + } + } + } else { + c.setDoipFlag(false); + } + } + + private void setContractStateful(Contract c) { + ContractManifest cm = YJSPacker.getManifest(c.getScriptStr()); + if (cm != null && cm.stateful != null && cm.stateful.equals("false")) { + c.setStateful(false); + } + } + + public String startContract(Contract c) { + return startContractAndRedirect(c, System.out); + } + + public String queryDEPort(String contractID) { + ContractClient client = getClient(contractID); + if (null == client) { + return "no such contract:" + contractID; + } else { + return client.get.syncGet("", "queryDEPort", ""); + } + } + + public String addDEMember(String contractID, String hostAndPort) { + ContractClient client = getClient(contractID); + if (null == client) { + return "no such contract:" + contractID; + } else { + return client.get.syncGet("", "addDEMember", hostAndPort); + } + } + + public String getScriptStrByID(String cid) { + ContractClient client = getClient(cid); + if (null != client) { + return client.contractMeta.contract.getScriptStr(); + } else { + return "contract " + cid + " not exists"; + } + } + + // TODO + public String requestContract(ContractRequest c) { + return "todo"; + } + + public synchronized ContractClient getClient(String idOrName) { + ContractClient client = statusRecorder.getContractClient(idOrName); + return client; + } + + public void clearCache() { + final long time = System.currentTimeMillis() - 30000L; + // LOGGER.info("try to clear cache, clear 0"); + /* reqCache.entrySet() + .removeIf( + entry -> { + RespCache cache = entry.getValue(); + if (cache == null) return true; + if (cache.count < 0) return true; + return cache.time < time; + }); + + */ + } + + synchronized RespCache getCache(String requestID) { + if (null != requestID && requestID.endsWith("_mul")) { + RespCache cache = reqCache.get(requestID); + if (null != cache) { + return cache; + } else { + LOGGER.debug("create cache:" + requestID); + RespCache resp = new RespCache(); + try { + resp.count = + Integer.parseInt( + requestID + .replaceFirst("[^_]*_", "") + .replaceAll("_.*$", "")); + } catch (Exception e) { + resp.count = 2; + } + // TODO 这个部分肯定要改哦。 + // A是requestAll模式的合约, + // B是授受调用者的,因此B需要检查A,B不能直接返回,需要"等待一半以上的commit?并且存下参数" + reqCache.put(requestID, resp); + LOGGER.debug("put into cache:" + requestID); + + return resp; + } + } + return null; + } + + public void executeLocallyAsync(ContractRequest c, final ResultCallback r, OnHashCallback cb) { + ContractResult cr; + String requestID = c.getRequestID(); + // 9000 + + if (null == c.getContractID()) { + LOGGER.info("合约Name转为ID error!"); + cr = + new ContractResult( + Status.Error, + new JsonPrimitive( + c.getContractID() + " Contract not Exists, CMLocallyAsync!")); + r.onResult(JsonUtil.toJson(cr)); + return; + } + // 在这里进行判断是否唤醒挂起的合约 + statusRecorder.ensureRunning(c); + + final ContractClient client = getClient(c.getContractID()); + if (null == client) { + cr = + new ContractResult( + Status.Error, + new JsonPrimitive( + c.getContractID() + " Contract not Exists, CMLocallyAsync!")); + r.onResult(JsonUtil.toJson(cr)); + return; + } + + long start = System.currentTimeMillis(); + // 9000 + + final RespCache cache = getCache(requestID); + // TODO 从LHS中获取该请求的公钥对应的运行节点的公钥列表 + // 等待1/2以上的运行节点公钥列表到达时,才进行下一步。 + if (null != cache) { + clearCache(); + if (cache.waitForHalf()) { + synchronized (cache) { + if (cache.val != null) { + LOGGER.info("[Hit Cache] " + requestID); + r.onResult(cache.val); + } else { + executeInternalAsync( + client, + c, + new ResultCallback() { + @Override + public void onResult(String str) { + cache.time = start; + cache.val = str; + r.onResult(cache.val); + cache.notifyAllWaiter(); + } + }, + cb); + + } + return; + } + } else { + ContractResult re = + new ContractResult( + Status.Error, + new JsonPrimitive("Timeout due to insufficient requester")); + r.onResult(JsonUtil.toJson(re)); + return; + } + } + // 9000 + + executeInternalAsync(client, c, r, cb); + } + + public String executeLocally(ContractRequest c, OnHashCallback cb) { + long start = System.currentTimeMillis(); + + // LOGGER.info( + // "[ContractManager] executeLocally : ContractRequest.requestID=" + // + c.getRequestID() + // + " tid:" + // + Thread.currentThread().getId()); + String requestID = c.getRequestID(); + if (c.getContractID() == null) { + LOGGER.info("合约Name转为ID error!"); + ContractResult cr = + new ContractResult( + Status.Error, + new JsonPrimitive( + c.getContractID() + " Contract not Exists, CMLocally!")); + return JsonUtil.toJson(cr); + } + + // TODO synchronized according to reqID? + ContractResult cr2; + ContractClient client = getClient(c.getContractID()); + if (client == null) { + cr2 = + new ContractResult( + Status.Error, + new JsonPrimitive( + c.getContractID() + " Contract not Exists, CMLocally!")); + return JsonUtil.toJson(cr2); + } + + // LOGGER.info("[ContractManager] executeLocally : requestID=" + requestID); + + RespCache cache = getCache(requestID); + // TODO 从LHS中获取该请求的公钥对应的运行节点的公钥列表 + // 等待1/2以上的运行节点公钥列表到达时,才进行下一步。 + if (null != cache) { + if (cache.waitForHalf()) { + synchronized (cache) { + if (cache.val != null) { + LOGGER.info("[Hit Cache] " + requestID); + return cache.val; + } else { + String result = executeInternal(client, c, cb); + cache.time = start; + cache.val = result; + cache.notifyAllWaiter(); + return result; + } + } + } else { + ContractResult re = + new ContractResult( + Status.Error, + new JsonPrimitive("Timeout due to insufficient requester")); + return JsonUtil.toJson(re); + } + } + + // LOGGER.info("cache is null"); + clearCache(); + return executeInternal(client, c, cb); + } + + private void executeInternalAsync( + ContractClient client, ContractRequest request, ResultCallback rcb, OnHashCallback cb) { + ContractResult cr; + + long start = System.currentTimeMillis(); + // 9000 + + if (client.contractMeta.sigRequired) { + if (!request.verifySignature()) { + cr = new ContractResult(Status.Error, new JsonPrimitive("sign verified failed")); + rcb.onResult(JsonUtil.toJson(cr)); + return; + } + } + client.times++; + client.contractStatus = ContractStatus.Executing; + ResultCallback acb; + // TODO acb bu dei jin + acb = + new ResultCallback() { + @Override + public void onResult(String result) { + client.traffic += result.length(); + client.contractStatus = ContractStatus.Executed; + + String finalRet = result; + if (client.getContractCopies() == 1) { + finalRet = + extractEventsFromContractResult( + cb, result, client, request, start); + } + rcb.onResult(finalRet); + if (finalRet.length() == result.length()) { + chainOpener.writeContractResultToLocalAndLedger( + finalRet, + client, + request, + cb, + start, + System.currentTimeMillis() - start); + } + if (client.contractMeta.getYjsType()==YjsType.Oracle){ + oracleCounter.inc(); + }else { + contractCounter.inc(); + } + totalCounter.inc(); + + } + }; + // if (ignoreLog) { + // rcb.onResult( + // + // "{\"needSeq\":false,\"seq\":0,\"status\":\"Success!!!!\",\"result\":\"world\","+ + // "\"isInsnLimit\":false,\"totalGas\":0,\"executionGas\":0,\"extraGas\":0,\"size\":0}"); + // return; + // } + + client.get.asyncGet("", "executeContract", JsonUtil.toJson(request), acb); + } + + private String executeInternal( + ContractClient client, ContractRequest request, OnHashCallback ocb) { + // LOGGER.info("ContractManager executeInternal : "); + + ContractResult cr; + long start = System.currentTimeMillis(); + if (client.contractMeta.sigRequired) { + if (!request.verifySignature()) { + cr = new ContractResult(Status.Error, new JsonPrimitive("sign verified failed")); + return JsonUtil.toJson(cr); + } + } + client.times++; + // 将合约状态改为“Executing” + client.contractStatus = ContractStatus.Executing; + // 占用一个线程的资源等待,如果现在处于recovering状态,ContractRequest放入队列,直接返回recovering结果 + String result = client.get.syncGet("", "executeContract", JsonUtil.toJson(request)); + LOGGER.debug(result); + + client.traffic += result.length(); + + String finalRet = result; + if (client.getContractCopies() == 1) { + finalRet = extractEventsFromContractResult(ocb, result, client, request, start); + } + if (finalRet.length() == result.length()) { + chainOpener.writeContractResultToLocalAndLedger( + result, client, request, ocb, start, System.currentTimeMillis() - start); + } + contractCounter.inc(); + return result; + } + + /** + * event related + * + * @author Kaidong Wu + */ + public String extractEventsFromContractResult( + OnHashCallback ocb, + String result, + ContractClient client, + ContractRequest request, + long startTime) { + String ret = result; + try { + ContractResult cr = JsonUtil.fromJson(result, ContractResult.class); + if (null != cr.events && !cr.events.isEmpty()) { + List msgList = cr.events; + cr.events = null; + ret = JsonUtil.toJson(cr); + chainOpener.writeContractResultToLocalAndLedger( + ret, + client, + request, + (reqID, hashStr) -> { + if (eventPersistenceEnabled) { + msgList.forEach( + msg -> { + msg.setTxHash(hashStr); + msg.doSignature( + client.getPubkey(), client.getContractKey()); + eventBroker.handle(msg); + }); + } + if (null != ocb) { + ocb.publishHash(reqID, hashStr); + } + }, + startTime, + System.currentTimeMillis() - startTime); + if (!eventPersistenceEnabled) { + threadPool.submit( + () -> + msgList.forEach( + e -> { + e.doSignature( + client.getPubkey(), + client.contractMeta.contract.getKey()); + eventBroker.handle(e); + })); + } + } + } catch (Exception ignored) { + } + return ret; + } + + public int countEvents() { + return eventBroker.countEvents(); + } + + public void removeContract(String contractID) { + statusRecorder.killContract(contractID); + } + + public boolean hasPID(int pid) { + return statusRecorder.hasPID(pid); + } + + public String execute(ContractRequest c, OnHashCallback cb) { + // return masterStub.executeGlobally(c, cb); + StrCollector resultCallback = new StrCollector(); + executeContractInternal(c, resultCallback, cb); + resultCallback.waitForResult(); + return resultCallback.strRet; + } + + public void executeContractInternal( + ContractRequest cr, final ResultCallback rcb, final OnHashCallback hcb) { + LOGGER.debug(JsonUtil.toJson(cr)); + ContractMeta meta = statusRecorder.getContractMeta(cr.getContractID()); + MultiContractMeta multiMeta = + multiContractRecorder.getMultiContractMeta(cr.getContractID()); + if (null == meta || meta.status == ContractStatusEnum.KILLED) { + executeContractOnOtherNodes(cr, rcb); + } else { + statusRecorder.ensureRunning(cr); + ContractClient client = statusRecorder.getContractClient(meta.id); + switch (client.getContractType()) { + case Sole: + executeLocallyAsync(cr, rcb, hcb); + return; + case RequestOnce: + case ResponseOnce: + case Shading: + masterStub.executeByMaster(client, rcb, cr); + break; + case RequestAllResponseAll: + case RequestAllResponseFirst: + case RequestAllResponseHalf: + if (multiMeta != null && multiMeta.isMaster()) { + masterStub.executeByMaster(client, rcb, cr); + } else { + executeContractOnOtherNodes(cr, rcb); + } + break; + default: + break; + } + } + } + + private void executeContractOnOtherNodes(ContractRequest cr, final ResultCallback rcb) { + ContractResult result; + if (null != nodeCenterConn && null != masterStub) { + String pubKey = nodeCenterConn.routeContract(cr.getContractID()); + // TODO 如果此时master正在选举中,先缓存请求,有必要吗? + // if(pubKey == null ||pubKey.equals("")){ + // logger.info("Master正在崩溃选举中,先将请求缓存!"); + // if(MasterClientRecoverMechAction.requestsToMaster == null){ + // MasterClientRecoverMechAction.requestsToMaster = new + // ConcurrentHashMap<>(); + // } + // + // if(!MasterClientRecoverMechAction.requestsToMaster.containsKey(c.getContractID())){ + // + // MasterClientRecoverMechAction.requestsToMaster.put(c.getContractID(),new + // Queue<>()); + // } + // + // MasterClientRecoverMechAction.requestsToMaster.get(c.getContractID()).add(new + // RequestToMaster(c)); + // return; + // } + + // LOGGER.info("查看合约 " + cr.getContractID() + " 的master为 " + pubKey); + + if (!masterStub.hasConnection(pubKey)) { + pubKey = nodeCenterConn.reRouteContract(cr.getContractID()); + LOGGER.info("查看合约 " + cr.getContractID() + " 的master为 " + pubKey); + } + + if (null != pubKey) { + masterStub.executeByOtherNodeAsync(pubKey, cr, rcb); + return; + // result = masterStub.executeByOtherNode(pubKey, cr); + } else { + result = + new ContractResult( + Status.Error, + new JsonPrimitive( + "Contract " + + cr.getContractID() + + "can't be located in router")); + // 告知NC重选 + /* + 好像有点问题,之前这么写是觉得找不到的话,是因为master出问题了,需要重选,但是没考虑到这个合约确实不存在 + 但是可能也不影响,如果这个合约确实不存在,那么即使NC发起了选举也不会真的进行选举 + */ + // JsonObject jo = new JsonObject(); + // jo.addProperty("action", "NCStartElect"); + // jo.addProperty("contractID", cr.getContractID()); + // nodeCenterConn.sendMsg(JsonUtil.toJson(jo)); + // LOGGER.info("发现合约 " + cr.getContractID() + " + // master为null,发请求给master令其发启选举"); + } + } else { + result = + new ContractResult( + Status.Error, + new JsonPrimitive( + "Contract " + cr.getContractID() + " doesn't exists!!")); + } + rcb.onResult(JsonUtil.toJson(result)); + } + + private ContractClient getByName(String contractName) { + return statusRecorder.getContractClient(contractName); + } + + public String stopContract(String contractID) { + return statusRecorder.killContract(contractID); + } + + // + public String stopContractWithOwner(final String owner, String contractID) { + LOGGER.info("[ContractManager] stopContractWithOwner : "); + boolean isNodeManager = checkNodeManager(owner); + ContractMeta meta = statusRecorder.getContractMeta(contractID); + if (meta != null) { + if (!meta.contract.getOwner().equals(owner) && !isNodeManager) { + return "Failed: Only the contract owner or node manager can stop a contract."; + } + return statusRecorder.killContract(meta); + } else { + return "contract:" + contractID + " not exists"; + } + } + + public String redirect(String contractID, PrintStream ps, String tag) { + ContractClient client = statusRecorder.getContractClient(contractID); + if (client.contractMeta.contract.isDebug()) { + client.outputTracer.redirect(ps, tag); + client.errorTracer.redirect(ps, tag); + return "success"; + } else { + return "failed, not debug mode"; + } + } + + public int stopAllContracts() { + return statusRecorder.stopAllContracts(); + } + + public int stopAllContractsWithOwner(final String owner) { + return statusRecorder.stopAllContractsWithOwner(owner); + } + + public String listTheContracts(String contractID) { + ContractMeta meta = statusRecorder.getContractMeta(contractID); + if (meta != null) { + ContractInfo info = fillContractInfo(meta); + return JsonUtil.toPrettyJson(info); + } + return "Contract Process not exist!"; + } + + private ContractInfo fillContractInfo(ContractMeta meta) { + ContractInfo info = new ContractInfo(); + info.type = meta.contract.getType(); + info.id = meta.contract.getID(); + info.name = meta.name; + info.contractStatus = meta.status; + info.annotations = meta.getAnnotations(); + info.exportedFunctions = meta.getExportedFunctions(); + info.events = meta.getEvents(); + info.yjsType = meta.getYjsType(); + info.pubkey = meta.getPubkey(); + info.contractPermission = meta.getThisPermission(); + + if (meta.status == ContractStatusEnum.RUNNING) { + ContractClient client = statusRecorder.getContractClient(meta.id); + info.port = String.valueOf(client.port); + client.memory = getMemoryByPID(client.getPID()); + info.times = client.times; + info.traffic = convertToBytes(client.traffic); + info.storage = convertToBytes(client.memory); + } + return info; + } + + private long getMemoryByPID(String pid) { + try { + long pidLong = Long.parseLong(pid); + if (sigar == null) { + sigar = new Sigar(); + } + ProcMem procMem = sigar.getProcMem(pidLong); + return procMem.getResident(); + } catch (Throwable e) { + return -1L; + } + } + + public String listContracts(final String firstID) { + List ret = new ArrayList<>(); + for (ContractMeta client : statusRecorder.getStatus().values()) { + if (client.status != ContractStatusEnum.KILLED) { + ContractInfo contractInfo = fillContractInfo(client); + ret.add(contractInfo); + } + } + sortContractInfoList(ret, firstID); + // System.out.println("[MsgHandler]" + + // JsonUtil.toPrettyJson(ret)); + return JsonUtil.toPrettyJson(ret); + } + + public String listContractsWithOwner(final String owner, final String firstID, int filters) { + boolean isNodeManager = checkNodeManager(owner); + List ret = new ArrayList<>(); + for (ContractMeta client : statusRecorder.getStatus().values()) { + if (!client.contract.getOwner().equals(owner) && !isNodeManager) { + continue; + } + if (0 == filters || (filters & (1 << client.status.getCode())) > 0) { + ContractInfo contractInfo = fillContractInfo(client); + ret.add(contractInfo); + } + } + sortContractInfoList(ret, firstID); + + // System.out.println("[MsgHandler]" + + // JsonUtil.toPrettyJson(ret)); + return JsonUtil.toPrettyJson(ret); + } + + void sortContractInfoList(List infoList, String firstID) { + infoList.sort( + (o1, o2) -> { + if (o1.id.equals(firstID)) { + return -1; + } + if (o2.id.equals(firstID)) { + return 1; + } + if (o1.name != null && o2.name != null) { + return o1.name.compareTo(o2.name); + } else { + return -1; + } + }); + } + + public String getContractResourceInfo() { + Map> cris = getContainerResourceInfoByTop(); + List ret = new ArrayList<>(); + for (ContractMeta client : statusRecorder.getStatus().values()) { + ContractInfo contractInfo = fillContractInfo(client); + ContractClient contractClient = statusRecorder.getContractClient(contractInfo.id); + if (contractClient != null) contractInfo.pid = contractClient.getPID(); + else contractInfo.pid = "-1"; + if (!contractInfo.pid.equals("-1") && !contractInfo.pid.equals("0")) { + try { + assert cris != null; + Map cri = cris.get(contractInfo.pid); + contractInfo.cpu = cri.get("%CPU"); + contractInfo.mem = cri.get("%MEM"); + contractInfo.rss = cri.get("MEM"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + ret.add(contractInfo); + } + // System.out.println("[MsgHandler]" + + // JsonUtil.toPrettyJson(ret)); + return JsonUtil.toJson(ret); + } + + public String getFreeResourceInfo() { + try { + Map freeResourceInfo = new HashMap<>(); + String freeCPU = null; + String freeMEM = null; + float totalMEM = 0; + Process process = Runtime.getRuntime().exec("top -b -n 1"); + InputStreamReader ir = new InputStreamReader(process.getInputStream()); + LineNumberReader input = new LineNumberReader(ir); + String line; + while ((line = input.readLine()) != null) { + // System.out.println(line); + if (line.contains("%Cpu(s):")) { + Scanner sc2 = new Scanner(new ByteArrayInputStream(line.getBytes())); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNextFloat()) freeCPU = sc2.nextFloat() + "%"; + sc2.close(); + } else if (line.contains("MiB Mem :")) { + Scanner sc2 = new Scanner(new ByteArrayInputStream(line.getBytes())); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNextFloat()) totalMEM = sc2.nextFloat(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNext()) sc2.next(); + if (sc2.hasNextFloat()) + freeMEM = String.format("%.1f", totalMEM - sc2.nextFloat()) + " MiB"; + sc2.close(); + } + } + freeResourceInfo.put("freeCPU", freeCPU); + freeResourceInfo.put("totalMEM", String.format("%.1f", totalMEM) + " MiB"); + freeResourceInfo.put("freeMEM", freeMEM); + return JsonUtil.toJson(freeResourceInfo); + } catch (IOException e) { + e.printStackTrace(); + return "getFreeResourceInfo failed."; + } + } + + private long safeParseLong(String result) { + try { + return Long.parseLong(result); + } catch (Exception e) { + return 0L; + } + } + + public String getGasEvaluates(String contractName, String functionName, String args) { + ContractClient client = getByName(contractName); + return client.get.syncGet("", "functionEvaluates", functionName); + } + + public String resetPermission(String contractName, String permission, String isOpen) { + ContractClient client = getByName(contractName); + String change = permission + "," + isOpen; + String setDesktopPermission = client.get.syncGet("", "setDesktopPermission", change); + client.contractMeta.thisPermission = client.get.syncGet("", "showPermission", ""); + return setDesktopPermission; + } + + public String resetDebugFlag(String contractName, boolean isDebug) { + ContractClient client = getByName(contractName); + String result = client.get.syncGet("", "changeDebugFlag", String.valueOf(isDebug)); + client.contractMeta.isDebug = isDebug; + return result; + } + /* + { + "name": "dx_substr", + "parameter": + { + "columnIndex":5, + "paras":["1","3"] + } + }, + */ + /*public String resetMask(String contractName, JsonObject MaskObject) { + ContractClient client = getByName(contractName); + System.out.println("contractName"+client.getContractName()); + + //String result = client.get.syncGet("", "changeDebugFlag", String.valueOf(isDebug)); + //設置clinet當中的mask client.contractMeta.isDebug = isDebug; + return "resetMask"; + }*/ + + public String dumpContract(String contractID, String path) { + // LOGGER.info(contractID); + // LOGGER.info(contracts.keySet().toString()); + ContractClient client = getClient(contractID); + + addLocalContractLog("dumpContract", client.contractMeta); + + // LOGGER.info(client.port); + // LOGGER.info(tmp); + return client.get.syncGet("", "getMemoryDump", path); + } + + public String redo(String contractID, String path) { + ContractClient client = getClient(contractID); + if (null == client) { + return "contractID " + contractID + " not exists"; + } + + addLocalContractLog("redo", client.contractMeta); + + return client.get.syncGet("", "redo", path); + } + + public String getCachedTransRecords(String contractID, String startSeq) { + ContractClient client = getClient(contractID); + if (null == client) { + // return "contractID " + contractID + " not exists"; + return ""; + } + + addLocalContractLog("getCachedTransRecords", client.contractMeta); + + return client.get.syncGet("", "getCachedTransRecords", startSeq); + } + + void addLocalContractLog(String content, ContractMeta meta) { + addLocalContractLog(content, meta.contract.getID(), meta.name, meta.contract.getOwner()); + } + + public String loadMemory(ContractClient client, String path) { + if (client == null) return "no such contract client"; + addLocalContractLog("loadMemory", client.contractMeta); + return client.get.syncGet("", "loadMemory", path); + } + + public List getContractDespList() { + List ret = new ArrayList<>(); + for (ContractMeta meta : statusRecorder.getStatus().values()) { + if (meta.status == ContractStatusEnum.RUNNING + || meta.status == ContractStatusEnum.HANGED) { + ContractDesp desc = new ContractDesp(); + desc.contractID = meta.id; + desc.contractName = meta.name; + desc.events = meta.declaredEvents; + desc.exportedFunctions = meta.exportedFunctions; + desc.type = meta.contract.getType(); + desc.annotations = meta.annotations; + desc.yjsType = meta.getYjsType(); + desc.dependentContracts = meta.getDependentContracts(); + MultiContractMeta multiContractMeta = + multiContractRecorder.getMultiContractMeta(meta.getID()); + desc.setIsMaster(multiContractMeta != null && multiContractMeta.isMaster()); + + ret.add(desc); + } + } + return ret; + } + + public String deliverEMessage(REvent msg) { + eventBroker.handle(msg); + return "success"; + } + + // ======log api + + public ContractResult getLogSize(String contractID) { + try { + ContractClient client = getClient(contractID); + if (client != null) { + return new ContractResult( + Status.Success, + new JsonPrimitive(client.get.syncGet("", "getLogSize", ""))); + } + // return new ContractResult(Status.Error, "cannot find contractID: " + contractID); + return new ContractResult( + Status.Error, new JsonPrimitive("cannot find contractID: " + contractID)); + } catch (Exception e) { + e.printStackTrace(); + return new ContractResult(Status.Exception, new JsonPrimitive(e.getMessage())); + } + } + + public ContractResult requestLog(String contractID, long offset, int count) { + try { + ContractClient client = getClient(contractID); + if (client != null) { + return new ContractResult( + Status.Success, + new JsonPrimitive( + client.get.syncGet("", "requestLog", offset + "," + count))); + } + return new ContractResult( + Status.Error, new JsonPrimitive("cannot find contractID: " + contractID)); + } catch (Exception e) { + e.printStackTrace(); + return new ContractResult(Status.Exception, new JsonPrimitive(e.getMessage())); + } + } + + public ContractResult requestLastLog(String contractID, int count) { + try { + ContractClient client = getClient(contractID); + if (client != null) { + return new ContractResult( + Status.Success, + new JsonPrimitive( + client.get.syncGet("", "requestLastLog", String.valueOf(count)))); + } + return new ContractResult( + Status.Error, new JsonPrimitive("cannot find contractID: " + contractID)); + } catch (Exception e) { + e.printStackTrace(); + return new ContractResult(Status.Exception, new JsonPrimitive(e.getMessage())); + } + } + + /* + public void addContractInfo(String name, String owner, String traffic, String times) { + KeyValueDBUtil.instance.setValue( + CMTables.ContractInfo.toString(), + name, + "{\"owner\":\"" + + owner + + "\",\"traffic\":\"" + + traffic + + "\",\"times\":\"" + + times + + "\"}"); + } + */ + + public void addLocalContractLog( + String action, String contractId, String contractName, String pubKey) { + String sb = + "{\"action\":\"" + + action + + "\",\"pubKey\":\"" + + pubKey + + "\",\"contractID\":\"" + + contractId + + "\",\"contractName\":\"" + + contractName + + "\",\"date\":" + + System.currentTimeMillis() + + "}"; + logsDB.put(contractName, sb); + } + + public void changePermission(String contractFileName, String pmList) { + } + + // public String getSyncType(String contractName) { + // ContractClient client = getByName(contractName); + // if (client == null) { + // return "No this contract process,failed"; + // } + // + // String result = client.get.syncGet("", "getSyncType", ""); + // + // addLocalContractLog( + // "getSyncType", + // client.contract.getID(), + // client.contractName, + // client.contract.getOwner()); + // return result; + // } + + // public String changeSyncType(String contractName, String type) { + // ContractClient client = getByName(contractName); + // if (client == null) { + // return "No this contract process,failed"; + // } + // + // String result = client.get.syncGet("", "changeSyncType", type); + // + // addLocalContractLog( + // "changeSyncType", + // client.contract.getID(), + // client.contractName, + // client.contract.getOwner()); + // return result; + // } + + // public String recoverBySync(String contractName) { + // ContractClient client = getByName(contractName); + // if (client == null) { + // return "No this contract process,failed"; + // } + // + // String result = client.get.syncGet("", "recoverBySync", ""); + // + // addLocalContractLog( + // "recoverBySync", + // client.contract.getID(), + // client.contractName, + // client.contract.getOwner()); + // return result; + // } + + public void addLocalContractLog( + String action, + String contractId, + String contractName, + String pubKey, + String function, + String costTime, + long totalGas, + long executionGas, + long extraGas, + Map logType) { + contractCounter.inc(); + if (logType == null || logType.isEmpty()) { + logsDB.put( + contractName, + String.format( + "{\"action\":\"%s\",\"pubKey\":\"%s\"," + + "\"contractID\":\"%s\",\"contractName\":\"%s\"," + + "\"function\":\"%s\",\"costTime\":\"%s\"," + + "\"totalGas\":\"%d\",\"executionGas\":\"%d\"," + + "\"extraGas\":\"%d\",\"date\":\"%d\"}", + action, + pubKey, + contractId, + contractName, + function, + costTime, + totalGas, + executionGas, + extraGas, + System.currentTimeMillis())); + } else { + StringBuilder str = + new StringBuilder() + .append("{\"action\":\"") + .append(action) + .append("\",\"pubKey\":\"") + .append(pubKey) + .append("\",\"contractID\":\"") + .append(contractId) + .append("\",\"contractName\":\"") + .append(contractName) + .append("\",\"function\":\"") + .append(function) + .append("\",\"costTime\":\"") + .append(costTime) + .append("\",\"totalGas\":\"") + .append(totalGas) + .append("\",\"executionGas\":\"") + .append(executionGas) + .append("\",\"extraGas\":\"") + .append(extraGas); + + for (String key : logType.keySet()) { + str.append("\",\"").append(key).append("\":\"").append(logType.get(key)); + } + str.append("\",\"date\":").append(System.currentTimeMillis()).append("}"); + logsDB.put(contractName, str.toString()); + } + } + + public String getTimesOfExecution(String contractName) { + return KeyValueDBUtil.instance.getValue( + CMTables.ContractInfo.toString(), contractName + "-Times"); + } + + public String getControlFlow(Contract c) { + if (null == analysisClient + || null == analysisClient.process + || !analysisClient.process.isAlive()) { + initAnalysisClient(); + } + return analysisClient.get.syncGet("", "getControlFlow", JsonUtil.toJson(c)); + } + + // 解析本地合约日志 + + public boolean findConflictOfName(String contractName) { + ContractMeta meta = statusRecorder.getContractMeta(contractName); + return meta != null && meta.status == ContractStatusEnum.RUNNING; + } + + public boolean findConflictOfDOI(String contractDOI) { + return null != statusRecorder.getContractMeta(contractDOI); + } + + public void clearSyncFiles(String contractID) { + ContractClient client = getClient(contractID); + if (client == null) { + return; + } + addLocalContractLog("clearSyncFiles", client.contractMeta); + client.get.syncGet("", "clearSyncFiles", contractID); + } + + public String setContractIsMaster(String contractID, String target) { + MultiContractMeta meta = multiContractRecorder.getMultiContractMeta(contractID); + if (null == meta) { + return null; + } + synchronized (meta) { + meta.setIsMaster(Boolean.parseBoolean(target)); + multiContractRecorder.updateValue(meta); + addLocalContractLog("setContractIsMaster", statusRecorder.getContractMeta(contractID)); + return "true"; + } + } + + public boolean getContractIsMaster(String contractID) { + MultiContractMeta meta = multiContractRecorder.getMultiContractMeta(contractID); + if (null == meta) { + return false; + } + return meta.isMaster(); + } + + public void startSync(String contractName) { + ContractClient client = getByName(contractName); + if (client == null) { + return; + } + client.get.syncGet("", "startSync", ""); + addLocalContractLog("startSync", client.contractMeta); + } + + public void stopSync(String contractName) { + ContractClient client = getByName(contractName); + if (client == null) { + return; + } + client.get.syncGet("", "stopSync", ""); + addLocalContractLog("stopSync", client.contractMeta); + } + + public void setCRFile(String contractName, String syncFileName) { + ContractClient client = getByName(contractName); + if (null == client) { + return; + } + client.get.syncGet("", "setCRFile", syncFileName); + addLocalContractLog("setCRFile", client.contractMeta); + } + + public void invokeContractSuicide(ContractClient client) { + if (null != client) { + try { + String ret = client.get.syncGet("", "suicide", ""); + JsonObject jo = JsonUtil.parseString(ret); + if (jo.has("cleanSub")) { + REvent msg = + new REvent( + null, + REvent.REventType.UNSUBSCRIBE, + "{\"subscriber\":\"" + client.getContractName() + "\"}", + ""); + msg.doSignature(client.getPubkey(), client.getContractKey()); + eventBroker.handle(msg); + } + } catch (Exception ignored) { + } + } + } + + public String parseYpkPermissions(String ypkPath) { + if (null == analysisClient + || null == analysisClient.process + || !analysisClient.process.isAlive()) { + initAnalysisClient(); + } + return analysisClient.get.syncGet("", "parseYpkPermissions", ypkPath); + } + + public String staticVerify(Contract c) { + if (null == analysisClient + || null == analysisClient.process + || !analysisClient.process.isAlive()) { + initAnalysisClient(); + } + return analysisClient.get.syncGet("", "staticVerify", JsonUtil.toJson(c)); + } + + // 合约状态 + + static class StrCollector extends ResultCallback { + String strRet = "{\"data\":\"Timeout\"}"; + boolean hasResult = false; + long start = System.currentTimeMillis(); + + @Override + public void onResult(String str) { + synchronized (this) { + strRet = str; + hasResult = true; + + this.notifyAll(); + } + } + + public void waitForResult() { + synchronized (this) { + try { + if (!hasResult) { + this.wait(5000); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void waitForResultCounter(int count) { + synchronized (this) { + try { + if (!hasResult) { + this.wait(20000L * count); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + static class Printer extends Thread { + Process p; + Scanner sc; + String TAG; + List psList; + Set toRemove; + + public void track(Process process, Scanner sc, String tag, PrintStream printStream) { + p = process; + this.sc = sc; + this.TAG = tag; + psList = new ArrayList<>(); + if (printStream != null) psList.add(printStream); + toRemove = new HashSet<>(); + start(); + } + + public synchronized void redirect(PrintStream ps) { + psList.add(ps); + } + + public void run() { + try { + while (sc.hasNextLine()) { + String content = sc.nextLine(); + if (psList.size() == 0) { + LOGGER.info(content); + } else { + printInList(content); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private synchronized void printInList(String content) { + for (PrintStream ps : psList) { + try { + ps.println(TAG + content); + } catch (Exception e) { + e.printStackTrace(); + toRemove.add(ps); + } + } + psList.removeAll(toRemove); + toRemove.clear(); + } + } // Printer + + static class ContractInfo { + public String pubkey; + String id, name, port; + ContractExecType type; + // 合约状态,调用次数,流量统计,内存占用 + String traffic, storage; + long times; + List exportedFunctions; + Map events; + ContractStatusEnum contractStatus; + String contractPermission; + List annotations; + + // 进程ID,占用CPU百分比,占用MEM百分比,占用物理内存byte数 + String pid; + String cpu; + String mem; + String rss; + YjsType yjsType; + } + + // public static void encryptContract(Contract c, String privKey) { + // String aes = AES2.generateAES(); + // RSA rsa = RSA.generateFromBase64(privKey); + // String scriptStr = new + // BASE64Encoder().encode(rsa.decode(aes.getBytes())); + // String scriptStr = new BASE64Encoder().encode(rsa.decode(aes.getBytes())); + // scriptStr += ","; + // scriptStr += AES2.encrypt(aes, c.getScriptStr()); + // c.setOwner(rsa.toBase64Pubkey()); + // c.setScript(scriptStr); + // } + // Used for ContractManager restart. + +} diff --git a/src/main/java/org/bdware/sc/ContractMeta.java b/src/main/java/org/bdware/sc/ContractMeta.java new file mode 100644 index 0000000..1c6587d --- /dev/null +++ b/src/main/java/org/bdware/sc/ContractMeta.java @@ -0,0 +1,119 @@ +package org.bdware.sc; + +import org.bdware.sc.bean.Contract; +import org.bdware.sc.bean.FunctionDesp; +import org.bdware.sc.bean.IDSerializable; +import org.bdware.sc.event.REvent; +import org.bdware.sc.node.AnnotationNode; +import org.bdware.sc.node.YjsType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ContractMeta implements IDSerializable { + public Contract contract; + ContractStatusEnum status; + String name; + String id; + boolean isDebug; + Map declaredEvents; + List exportedFunctions; + Map logDetail; + List annotations; + Set dependentContracts; + transient Map funCache; + boolean sigRequired; + String thisPermission; // 合约当前权限 + /* + { + "name": "dx_substr", + "parameter": + { + "columnIndex":5, + "paras":["1","3"] + } + }, + */ + // MapMaskInfo; + + public ContractMeta() {} + + public ContractStatusEnum getStatus() { + return status; + } + + public String getID() { + return id; + } + + public List getAnnotations() { + return annotations; + } + + public List getExportedFunctions() { + return exportedFunctions; + } + + public Set getDependentContracts() { return dependentContracts; } + + public Map getEvents() { + return declaredEvents; + } + + public YjsType getYjsType() { + return contract.getYjsType(); + } + + public String getPubkey() { + return contract.getPublicKey(); + } + + public String getThisPermission() { + return thisPermission; + } + + public String getName() { + return name; + } + + public boolean getIsDebug() { + return isDebug; + } + + public FunctionDesp getExportedFunction(String action) { + if (funCache == null) funCache = new HashMap<>(); + FunctionDesp desp = funCache.get(action); + if (desp != null) return desp; + desp = seekFunction(action); + if (desp != null) funCache.put(action, desp); + return desp; + } + // public setMask(){ + ; + + // } + + private FunctionDesp seekFunction(String action) { + for (FunctionDesp desp : exportedFunctions) { + if (desp != null && desp.functionName.equals(action)) return desp; + } + return null; + } + + public void setContract(Contract c) { + contract = c; + id = c.getID(); + status = ContractStatusEnum.HANGED; + isDebug = false; + } + + public void setName(String name) { + this.name = name; + } + + public void setStatus(ContractStatusEnum status) { + this.status = status; + } +} diff --git a/src/main/java/org/bdware/sc/ContractStatusEnum.java b/src/main/java/org/bdware/sc/ContractStatusEnum.java new file mode 100644 index 0000000..fc7d23a --- /dev/null +++ b/src/main/java/org/bdware/sc/ContractStatusEnum.java @@ -0,0 +1,43 @@ +package org.bdware.sc; + +public enum ContractStatusEnum { + INIT(0, "初始化"), + RUNNING(1, "内存运行中"), + HANGED(2, "硬盘运行中"), + KILLED(3, "已停止"); + private Integer code; + private String desc; + + ContractStatusEnum(Integer code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ContractStatusEnum getContractStatusEnumByCode(Integer code) { + if (code == null) { + return null; + } + for (ContractStatusEnum contractStatusEnum : ContractStatusEnum.values()) { + if (contractStatusEnum.getCode().intValue() == code) { + return contractStatusEnum; + } + } + return null; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/src/main/java/org/bdware/sc/ContractStatusRecorder.java b/src/main/java/org/bdware/sc/ContractStatusRecorder.java new file mode 100644 index 0000000..d578d7c --- /dev/null +++ b/src/main/java/org/bdware/sc/ContractStatusRecorder.java @@ -0,0 +1,423 @@ +package org.bdware.sc; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.ContractPort.PortVisitor; +import org.bdware.sc.bean.Contract; +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.StatusRecorder; +import org.bdware.sc.util.JsonUtil; +import org.bdware.sc.util.LRUList; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author xiaoche (2021/4/25 18:05) + */ +public class ContractStatusRecorder extends StatusRecorder { + private static final String PREFIX = "CONTRACT_INFO_META_"; + private static final Logger LOGGER = LogManager.getLogger(ContractStatusRecorder.class); + public static AtomicInteger contractVersion = new AtomicInteger(0); + // 对外会有一个getContractClient的过程,如果只是想用元信息的,改成getContractClientMeta + // 如果是想调用的,再使用getContractClient。 + + // 合约执行的状态 key:合同id value:合同状态 + // private static final String DB_NAME = CMTables.ContractInfo.toString(); + + static { + final Object flag = new Object(); + // 调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调 + ContractManager.scheduledThreadPool.scheduleWithFixedDelay( + () -> { + boolean cleared = dealTimerContractProcess(); + if (cleared) { + synchronized (flag) { + try { + flag.wait(14000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }, + 0, + 1, + TimeUnit.SECONDS); + } + + // contract id to client, changes when some contract is started/stopped/resumed + private final Map id2ContractClient; + + public LRUList runningProcess; + + public ContractStatusRecorder(String dir) { + super(dir, CMTables.ContractInfo.toString(), PREFIX); + id2ContractClient = new ConcurrentHashMap<>(); + runningProcess = new LRUList<>(); + LOGGER.info("Load Done!"); + LOGGER.debug("Load Done: " + JsonUtil.toJson(getStatus())); + } + + public static boolean dealTimerContractProcess() { + // if (id2ContractStatus == null) { + // String value = readDataFromDB(id2ContractStatusKey); + // id2ContractStatus = GSON.fromJson(value, Map.class); + // if (id2ContractStatus == null) { + // return true; + // } + // for (Map.Entry contractStatusEnumEntry : + // id2ContractStatus.entrySet()) { + // //获取合同id + // String contractID = contractStatusEnumEntry.getKey(); + // ContractStatusEnum contractStatusEnum = + // contractStatusEnumEntry.getValue(); + // //没有状态信息就停止 + // if (contractStatusEnum == null) { + // continue; + // } + // //拥有合同状态信息的,如果是已经执行结束,我们将合同挂起 + // if + // (contractStatusEnum.getCode().equals(ContractStatusEnum.STORE.getCode())) { + // resumeContractProcess(contractID); + // } else if + // (contractStatusEnum.getCode().equals(ContractStatusEnum.DONE.getCode())) { + // hangUpContractProcess(contractID); + // } + // } + // } + return true; + } + + private static String resumeStartContract(Contract contract) { + return ContractManager.instance.startContractAndRedirect(contract, null); + } + + public boolean hasPID(int pid) { + // TODO 是不是还有别的?这只是在内存里的哦。 + try { + for (ContractClient c : id2ContractClient.values()) { + if (Integer.parseInt(c.getPID()) == pid) return true; + } + } catch (Exception e) { + // e.printStackTrace(); + } + return false; + } + + public void reSyncStatusAtStart() { + List toPrint = new ArrayList<>(); + for (ContractMeta meta : getStatus().values()) { + toPrint.add(meta.status + " " + meta.id + " " + meta.name + " "); + } + LOGGER.debug(JsonUtil.toPrettyJson(toPrint)); + + for (String id : id2ContractClient.keySet()) { + ContractMeta meta = getStatus().get(id); + if (null == meta) { + LOGGER.error("unknown id in meta db"); + } + if (meta != null && meta.status != ContractStatusEnum.RUNNING) { + meta.status = ContractStatusEnum.RUNNING; + updateValue(meta); + } + } + + for (ContractMeta meta : getStatus().values()) { + if (meta.status == ContractStatusEnum.RUNNING) { + if (id2ContractClient.get(meta.getID()) == null) { + meta.status = ContractStatusEnum.HANGED; + updateValue(meta); + } + } + } + } + + public void createContract(ContractClient client) { + ContractMeta meta = client.contractMeta; + contractVersion.getAndIncrement(); + meta.status = ContractStatusEnum.RUNNING; + updateValue(meta); + id2ContractClient.put(meta.id, client); + runningProcess.add(meta); + } + + public void hangLeastUsedContractProcess() { + ContractMeta meta = runningProcess.popOldest(); + while (null != meta && ContractStatusEnum.RUNNING != meta.status) { + meta = runningProcess.popOldest(); + } + if (null != meta) { + hangUpContractProcess(meta.id); + } + } + + // TODO index name to contract + public ContractMeta getContractMeta(String idOrNameOrDOI) { + if (idOrNameOrDOI == null) return null; + ContractMeta meta = getStatus().get(idOrNameOrDOI); + if (meta != null) return meta; + for (ContractMeta cc : getStatus().values()) { + if (cc.status == ContractStatusEnum.RUNNING || cc.status == ContractStatusEnum.HANGED) + if (idOrNameOrDOI.equals(cc.name) || idOrNameOrDOI.equals(cc.contract.getDOI())) { + return cc; + } + } + for (ContractMeta cc : getStatus().values()) { + if (idOrNameOrDOI.equals(cc.name) || idOrNameOrDOI.equals(cc.contract.getDOI())) { + return cc; + } + } + return null; + } + + public int stopAllContracts() { + List ids = new ArrayList<>(getStatus().keySet()); + for (String key : ids) { + killContract(key); + } + id2ContractClient.clear(); + return ids.size(); + } + + public String killContract(String idOrName) { + ContractMeta meta = getContractMeta(idOrName); + if (meta != null) return killContract(meta); + return "no such contract " + idOrName; + } + + public String killContract(ContractMeta meta) { + contractVersion.getAndIncrement(); + meta.status = ContractStatusEnum.KILLED; + updateValue(meta); + ContractClient client = id2ContractClient.get(meta.id); + if (client != null) { + client.saveTimesAndTraffic(); + String ff = client.get.syncGet("", "getMemorySet", ""); + LOGGER.info("[killContract] memorySet : " + ff); + if (null != ff && ff.contains("kill")) { + // String[] strings = ff.split(";"); + LOGGER.info("need dump when stop"); + String contractName = client.getContractName(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd.HH:mm:ss"); + File f = + new File( + ContractManager.dir + "/memory/" + contractName, + df.format(new Date())); + client.get.syncGet("", "getMemoryDump", f.getAbsolutePath()); + ContractManager.instance.addLocalContractLog("dumpContract", meta); + } + ContractManager.instance.invokeContractSuicide(client); + ContractManager.cPort.updateDb(client.port, false); + } + id2ContractClient.remove(meta.id); + runningProcess.remove(meta); + return "success"; + } + + public int stopAllContractsWithOwner(String owner) { + List toKill = new ArrayList<>(); + for (ContractMeta meta : getStatus().values()) { + if (meta.contract.getOwner().equals(owner)) { + toKill.add(meta); + } + } + for (ContractMeta meta : toKill) { + killContract(meta); + } + return toKill.size(); + } + + public ContractClient getContractClient(String idOrName) { + if (StringUtils.isBlank(idOrName)) { + return null; + } + // TODO ensure load contract client + // TODO ensure load contract client + // TODO ensure load contract client + // TODO ensure load contract client + ContractMeta meta = getContractMeta(idOrName); + if (null == meta) { + LOGGER.error("meta of contract " + idOrName + " not found!"); + return null; + } + return id2ContractClient.get(meta.id); + } + + public void ensureRunning(ContractRequest contractRequest) { + // 获取合同id + String contractID = contractRequest.getContractID(); + ContractMeta meta = getContractMeta(contractID); + contractID = meta.id; + // 判断目前合同的状态信息 + if (meta.status == ContractStatusEnum.HANGED) { + resumeContractProcess(contractID); + } + // 似乎这样就可以了? + // } else if (meta.status == ContractStatusEnum.RUNNING) { + // hangUpContractProcess(contractID); + // } + } + + /** + * 解决思路: 1、CMActions#在executeContract 执行ContractStatusRecorder. + */ + public synchronized void hangUpContractProcess(String contractID) { + // dump stop 这里是正常流程代码 + // ContractClient contractClient = getContractClientById(contractID); + // if (contractClient == null) { + // return; + // } + // //挂起 + // Contract contract = contractClient.contract; + // ContractManager.instance.dumpContract(contract.getID(), null); + // setContractClient(contractID, contractClient); + // ContractManager.instance.hangUpStopContract(contract.getID()); + // todo 临时想强制测试下效果 + ContractMeta contractMeta = getContractMeta(contractID); + // 挂起 + Contract contract = contractMeta.contract; + // System.out.println("hangUp"+contract.Mask); + if (contractMeta.status == ContractStatusEnum.RUNNING) { + contractMeta.status = ContractStatusEnum.HANGED; + updateValue(contractMeta); + ContractClient client = id2ContractClient.get(contractMeta.id); + if (null != client) { + client.saveTimesAndTraffic(); + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd.HH_mm_ss"); // 设置日期格式 + File f = + new File( + ContractManager.dir + "/memory/" + contractMeta.name, + df.format(new Date())); + File parent = f.getParentFile(); + if (!parent.exists()) { + LOGGER.trace("make directory " + parent.getAbsolutePath() + ":" + parent.mkdirs()); + } + ContractManager.instance.dumpContract(contract.getID(), f.getAbsolutePath()); + ContractManager.instance.invokeContractSuicide(client); + id2ContractClient.remove(contractMeta.getID()); + runningProcess.remove(contractMeta); + } + } + } + + public synchronized void resumeContractProcess(String contractID) { + // 从参数获取合同名称 + ContractMeta meta = getContractMeta(contractID); + // 启动合约 + // 加载所需要的内存 + // 该方法调用方法已经考虑了内存问题,但是内存不足的时候,重试或者等待,或者将合约压到失败中稍后重试,可以尝试设计消息重试机制 + // 如果是内存中则唤起合同信息 todo 内存不足 关闭合约追赶,补偿数据 + if (meta.status == ContractStatusEnum.HANGED || meta.status == ContractStatusEnum.KILLED) { + ContractStatusEnum preStatus = meta.status; + // TODO Resume里复用的是start逻辑,启动完了就成Running了哦。可能不能这么来。 + String startContractResult = resumeStartContract(meta.contract); + ContractResult contractResult = + JsonUtil.fromJson(startContractResult, ContractResult.class); + if (contractResult.status == ContractResult.Status.Error) { + // 记录错误日志 + ContractManager.instance.addLocalContractLog( + "resumeContract error", + meta.contract.getID(), + meta.name, + meta.contract.getOwner()); + } else { + ContractManager.instance.addLocalContractLog( + "resumeContract success", + meta.contract.getID(), + meta.name, + meta.contract.getOwner()); + // TODO 可能会重复load相同的镜像? + // 如果是killed 只根据manifest去判断是否加载memory + // 可增加一个判断,如果hanged朋manifest里是加载memory,这里就不再加载了。 + ContractClient client = id2ContractClient.get(meta.getID()); + if (preStatus == ContractStatusEnum.HANGED) { + + ContractManager.instance.loadMemory( + client, ContractManager.instance.findNewestMemory(meta.name)); + + + } + client.contractMeta.setStatus(ContractStatusEnum.RUNNING); + updateValue(client.contractMeta); + // 还要判断一发,是不是已有permission的值。如果有,要setPermission一把。 + // meta.status = ContractStatusEnum.RUNNING; + // updateValue(meta); + } + } + } + + public PortVisitor getVisitor() { + return new ReconnectVisitor(); + } + + class ReconnectVisitor implements PortVisitor { + private final Set existed = new HashSet<>(); // 已经连上的合约端口 + private final Set contractIDs = new HashSet<>(); // 已经连上的合约id + + public ReconnectVisitor() { + for (ContractClient c : id2ContractClient.values()) { + existed.add(c.port); + } + } + + @Override + public boolean visit(int i) { + if (i == ContractManager.cPort.getCMPort()) { + return false; + } + if (existed.contains(i)) { + return true; + } + try { + ContractClient client = new ContractClient(i); + // LOGGER.info("[CM重连] position-----1"); + if (client.isRunning) { + LOGGER.debug("CP listened to port " + i + " is running"); + if (contractIDs.contains(client.getContractID())) { + LOGGER.info("find contract process with duplicate contractID, kill!"); + ContractManager.instance.invokeContractSuicide(client); + return false; + } + contractIDs.add(client.getContractID()); + if (client.contractMeta.name.equals("analysis_client")) { + LOGGER.info("kill analysis_client"); + ContractManager.instance.invokeContractSuicide(client); + return false; + } + LOGGER.debug( + String.format( + "CP listened to port %d:\n\tID=%s\n\tName=%s\n\tType=%s\n" + + "\tKey=%s\n\tPubKey=%s\n\tCopies=%d\n\t%s", + i, + client.getContractID(), + client.contractMeta.name, + client.getContractType(), + client.getContractKey(), + client.getPubkey(), + client.getContractCopies(), + client.contractMeta.contract.startInfo)); + client.get.syncGet("", "setDir", ContractManager.dir); + // String str = client.getIdentifier(); + client.get.syncGet("", "getDumpPeriod", "a"); + client.get.syncGet("", "startAutoDump", "a"); + createContract(client); + LOGGER.info( + String.format("reconnect to port %d: contract %s", + i, client.contractMeta.name)); + return true; + } + } catch (Exception e) { + //just ignore + } + return false; + } + } +} diff --git a/src/main/java/org/bdware/sc/MasterElectTimeRecorder.java b/src/main/java/org/bdware/sc/MasterElectTimeRecorder.java new file mode 100644 index 0000000..49cb473 --- /dev/null +++ b/src/main/java/org/bdware/sc/MasterElectTimeRecorder.java @@ -0,0 +1,15 @@ +package org.bdware.sc; + +import java.util.HashMap; +import java.util.Map; + + +//Node +public class MasterElectTimeRecorder { + public static Long findMasterCrash; //发现master崩溃的时间 + public static Long newMasterStart; //作为新的master newMasterStart被调用时间 + public static Long setLocalMaster; //在本地标记自己为master + public static Long slaveConnectFinish; //被slave连接完成 + public static Long masterStartRecover; //开始master恢复 + public static Long masterRecoverFinish; //master恢复结束 +} diff --git a/src/main/java/org/bdware/sc/MasterStub.java b/src/main/java/org/bdware/sc/MasterStub.java new file mode 100644 index 0000000..69aa8fa --- /dev/null +++ b/src/main/java/org/bdware/sc/MasterStub.java @@ -0,0 +1,15 @@ +package org.bdware.sc; + +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.ResultCallback; + +public interface MasterStub { + // String executeGlobally(ContractRequest c, OnHashCallback cb); + void executeByMaster(ContractClient client, ResultCallback rcb, ContractRequest c); + + void transferToOtherNode(String pubKey, String contractID); + + void executeByOtherNodeAsync(String pubKey, ContractRequest c, ResultCallback cb); + + boolean hasConnection(String pubKey); +} diff --git a/src/main/java/org/bdware/sc/MultiContractRecorder.java b/src/main/java/org/bdware/sc/MultiContractRecorder.java new file mode 100644 index 0000000..601dbac --- /dev/null +++ b/src/main/java/org/bdware/sc/MultiContractRecorder.java @@ -0,0 +1,47 @@ +package org.bdware.sc; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.KeyValueDBUtil; +import org.bdware.sc.db.StatusRecorder; +import org.bdware.sc.units.MultiContractMeta; + +public class MultiContractRecorder extends StatusRecorder { + static final String dbName = CMTables.UnitContracts.toString(); + static final String prefix = "Multi_C_Meta_"; + private static final Logger LOGGER = LogManager.getLogger(MultiContractRecorder.class); + + public MultiContractRecorder(String dir) { + super(dir, dbName, prefix); + for (MultiContractMeta meta : getStatus().values()) { + try { + meta.initQueue(); + int lastExeSeq = + Integer.parseInt( + KeyValueDBUtil.instance.getValue( + CMTables.LastExeSeq.toString(), meta.getContractID())); + meta.setLastExeSeq(lastExeSeq); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public MultiContractMeta getMultiContractMeta(String idOrNameOrDOI) { + if (idOrNameOrDOI == null) return null; + ContractMeta meta = ContractManager.instance.statusRecorder.getContractMeta(idOrNameOrDOI); + if (meta == null) return null; + return getStatus().get(meta.id); + } + + public MultiContractMeta createIfNotExist(String contractID) { + MultiContractMeta ret = getMultiContractMeta(contractID); + if (null == ret) { + LOGGER.info("requests don't contain contract " + contractID); + ret = new MultiContractMeta(contractID); + updateValue(ret); + } + return ret; + } +} diff --git a/src/main/java/org/bdware/sc/ProjectRecorder.java b/src/main/java/org/bdware/sc/ProjectRecorder.java new file mode 100644 index 0000000..b7aa912 --- /dev/null +++ b/src/main/java/org/bdware/sc/ProjectRecorder.java @@ -0,0 +1,46 @@ +package org.bdware.sc; + +import org.bdware.sc.bean.ProjectConfig; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.StatusRecorder; + +public class ProjectRecorder extends StatusRecorder { + static final String dbName = CMTables.ProjectConfig.toString(); + static final String prefix = "Project_Config_"; +// private static final Logger LOGGER = LogManager.getLogger(ProjectRecorder.class); + + public ProjectRecorder(String dir) { + super(dir, dbName, prefix); + } + + public ProjectConfig getProjectConfig(String idOrNameOrDOI) { + if (null == idOrNameOrDOI) { + return null; + } + ContractMeta meta = ContractManager.instance.statusRecorder.getContractMeta(idOrNameOrDOI); + if (meta == null) { + return null; + } + if (null == meta.contract.sourcePath) { + if (meta.contract.getScriptStr().contains("public")) { + meta.contract.sourcePath = "public/" + meta.name; + } else { + meta.contract.sourcePath = meta.contract.getPublicKey() + "/" + meta.name; + } + } + ProjectConfig config = getStatus().get(meta.contract.sourcePath); + if (config == null) { + return createDefaultConfig(meta.contract.sourcePath); + } else { + return config; + } + } + + public ProjectConfig createDefaultConfig(String sourcePath) { + ProjectConfig config = new ProjectConfig(sourcePath); + + updateValue(config); + return config; + } + +} diff --git a/src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java b/src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java new file mode 100644 index 0000000..59849b7 --- /dev/null +++ b/src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java @@ -0,0 +1,44 @@ +package org.bdware.sc; + +import java.util.HashMap; +import java.util.Map; + +public class RecoverMechTimeRecorder { + //slave记录 + public static Long startCMHttpServer; //启动 + public static Long startFinish; //启动完成,开始连接NC + public static Long connectNCFinish; //连接NC完成 + + public static Long startReconnectCP; //开始重连CP + public static Long reconnectCPFinish; //重连CP完成 + + public static Long startQueryMaster; //开始查看master是谁 + public static Long queryMasterFinish; //查到master是谁,开始连接master + public static Long connectMasterFinish; //连接master完成 + + public static Long startRecover; //开始恢复,slave的askForRecover被调用 + + public static Long startCommonRecover; //slave开始从common恢复 + public static Long startStableRecover; //slave开始从stable恢复 + + public static Long stableLoadFinish; //stable恢复模式load完成 + public static Long stableRedoFinish; //stable恢复模式redo完成 + + public static Long startRedoTransFromMaster; //redo从master传来的trans + + + + //master记录,key是slave的pubKey + public static Map masterStartRecoverNode = new HashMap(); //master的askForRecover被调用 + + public static Map startJudgeRecoverMethod = new HashMap(); //开始判断某个节点的恢复方式 + public static Map judgeRecoverMethodFinish = new HashMap(); //判断某节点恢复方式完成 + + public static Map startRecoverFromCommon = new HashMap(); //slave从common恢复master的nodeRestartFromCommonMode方法开始时间 + public static Map writeCEIStart = new HashMap(); //开始dumpContract等cei信息 + public static Map finishWriteCEI = new HashMap(); //将cei写入文件完成 + + public static Map startRecoverFromStable = new HashMap(); //slave从common恢复master的restartFromStableMode方法开始时间 + + public static Map recoverFinish = new HashMap(); +} diff --git a/src/main/java/org/bdware/sc/event/EventBroker.java b/src/main/java/org/bdware/sc/event/EventBroker.java new file mode 100644 index 0000000..408d212 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/EventBroker.java @@ -0,0 +1,479 @@ +package org.bdware.sc.event; + +import com.google.gson.JsonObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.ContractClient; +import org.bdware.sc.ContractManager; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.event.clients.ContractConsumer; +import org.bdware.sc.event.clients.IEventConsumer; +import org.bdware.sc.event.clients.NodeConsumer; +import org.bdware.sc.util.HashUtil; +import org.bdware.sc.util.JsonUtil; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static org.bdware.sc.event.REvent.REventSemantics.NEED_RETRY; +import static org.bdware.sc.event.REvent.REventSemantics.ONLY_ONCE; +import static org.bdware.sc.event.REvent.REventType.*; + +/** + * @author Kaidong Wu + */ +public class EventBroker { + private static final Logger LOGGER = LogManager.getLogger(EventBroker.class); + private static final long EXPIRED_TIME = 90 * 1000L; + + private final EventCenter center; + private final EventRecorder recorder; + private final Map> topic2cIds; + private final Map id2Consumers; + private final Map threadFlags; + private final Map tempTopics; + // private final Map> client2Events; + + public EventBroker() { + center = new EventCenter(); + recorder = new EventRecorder(CMTables.EventRegistry.toString(), this); + + threadFlags = new ConcurrentHashMap<>(); + + // recover registries from database + EventRecorder.CheckPoint cp = recorder.recoverRegistryFromDb(); + topic2cIds = cp.topic2cIds; + id2Consumers = cp.id2Consumers; + tempTopics = recorder.recoverTempTopicsFromDb(); + + // regularly check temporary topics and clean them + ContractManager.scheduledThreadPool.scheduleWithFixedDelay( + () -> { + long current = System.currentTimeMillis(); + int oldSize = tempTopics.size(); + tempTopics.keySet().forEach(topic -> { + if (tempTopics.get(topic) + EXPIRED_TIME > current) { + String reqID = + ContractManager.instance.nodeCenterConn.getNodeKeyPair().getPublicKeyStr() + + "_" + System.currentTimeMillis(); + REvent cleanEvent = + new REvent( + topic, + UNSUBSCRIBE, + null, + reqID); + cleanEvent.doSignature(ContractManager.instance.nodeCenterConn.getNodeKeyPair()); + handle(cleanEvent); + tempTopics.remove(topic); + } + }); + if (oldSize != tempTopics.size()) { + recorder.saveTempTopics(tempTopics); + } + }, + 0L, + EXPIRED_TIME, + TimeUnit.MILLISECONDS); + // regularly create check point in database + ContractManager.scheduledThreadPool.scheduleAtFixedRate( + () -> recorder.createCheckPoint(topic2cIds, id2Consumers), + EXPIRED_TIME, + EXPIRED_TIME, + TimeUnit.MILLISECONDS); + + NodeConsumer.setCenter(center); + +// client2Events = new HashMap<>(); + LOGGER.info("Event Broker starts!"); + } + + /** + * handle the event after checking the signature + * + * @param event event request + */ + public void handle(REvent event) { + if (!event.verifySignature()) { + LOGGER.debug(JsonUtil.toJson(event)); + return; + } + String topic = event.getTopic(); + switch (event.getType()) { + case SUBSCRIBE: + if (null != topic && !topic.isEmpty()) { + doSubscribe(event); + // save & try to sub in center + recorder.appendEvent(event); + center.subInCenter(event.getTopic(), event.getSemantics()); + } + break; + case UNSUBSCRIBE: + doUnsubscribe(event); + recorder.appendEvent(event); + break; + case PUBLISH: + case PREPUB: + case PRESUB: + LOGGER.info(String.format("Receive %s event from topic %s", event.getSemantics(), topic)); + LOGGER.debug(String.format("Receive %s event %s: %s", + event.getSemantics(), topic, event.getContent())); + if (event.isForward()) { + // send event to the event center + event.setForward(center.deliverEvent(topic, event)); + } + // if the event is from or in event center, save and do publishing + if (!event.isForward()) { + recorder.appendEvent(event); + doPublish(event); + } + default: + break; + } + } + + private void doSubscribe(REvent e) { + subInReg(e.getTopic(), parseConsumer(e.getContent()), this.topic2cIds, this.id2Consumers); + // for events with semantics ONLY_ONCE, mark the topic is a temporary topic + if (ONLY_ONCE.equals(e.getSemantics()) && !tempTopics.containsKey(e.getTopic())) { + tempTopics.put(e.getTopic(), System.currentTimeMillis()); + recorder.saveTempTopics(tempTopics); + } + } + + /** + * do subscribing in registry + * + * @param topic event topic + * @param consumer the consumer + * @param topic2cIds topic registry of broker or a check point in event recorder + * @param id2Consumers consumer registry of broker or a check point in event recorder + * @return if the subscribing succeeds + */ + public boolean subInReg( + String topic, + IEventConsumer consumer, + Map> topic2cIds, + Map id2Consumers) { + if (null == consumer) { + return false; + } + String cId = consumer.getId(); + if (!id2Consumers.containsKey(cId)) { + id2Consumers.put(cId, consumer); + } + if (!topic2cIds.containsKey(topic)) { + topic2cIds.put(topic, new HashSet<>()); + } + topic2cIds.get(topic).add(cId); + if (consumer instanceof ContractConsumer) { + LOGGER.info("contract " + ((ContractConsumer) consumer).getContract() + " subscribes topic " + topic); + } else { + LOGGER.info("node " + consumer.getId() + " subscribes topic " + topic); + } + return true; + } + + private void doUnsubscribe(REvent e) { + unsubInReg(e.getTopic(), parseConsumer(e.getContent()), this.topic2cIds, this.id2Consumers); + } + + /** + * do subscribing in registry
+ * topic and consumer must not be null at the same time + *
    + *
  • if consumer is null and topic is not, it means the topic is a temporary topic, remove it
  • + *
  • if topic is null and consumer is not, + * it means a consumer or a contract wants to unsubscribe all topics, + * remove all related consumers in topic registry and consumer registry
  • + *
  • if two of them is not null, do unsubscribing in two registries
  • + *
+ * + * @param topic event topic + * @param consumer the consumer, just id is required + * @param topic2cIds topic registry of broker or a check point in event recorder + * @param id2Consumers consumer registry of broker or a check point in event recorder + * @return if the subscribing succeeds + */ + public boolean unsubInReg( + String topic, + IEventConsumer consumer, + Map> topic2cIds, + Map id2Consumers) { + if (null == topic && null == consumer) { + return false; + } + if (null == consumer) { + topic2cIds.remove(topic); + LOGGER.info("clean temporary topic " + topic); + return true; + } + String cId = consumer.getId(); + List toRmIds; + String contract; // just used for log + if (id2Consumers.containsKey(cId)) { + // if cId belongs to a contract consumer, use the cId as a singleton list + toRmIds = Collections.singletonList(cId); + contract = ((ContractConsumer) id2Consumers.get(cId)).getContract(); + } else { + // if cId belongs to a contract, find all related consumers + toRmIds = new ArrayList<>(); + id2Consumers.forEach((k, c) -> { + if (c instanceof ContractConsumer && ((ContractConsumer) c).getContract().equals(cId)) { + toRmIds.add(k); + } + }); + contract = cId; + } + if (toRmIds.isEmpty()) { + return true; + } + if (null == topic || topic.isEmpty()) { + topic2cIds.values().forEach(cIds -> toRmIds.forEach(cIds::remove)); + toRmIds.forEach(id2Consumers::remove); + LOGGER.info("contract " + contract + " unsubscribes all topics"); + } else if (topic2cIds.containsKey(topic)) { + Set topic2Id = topic2cIds.get(topic); + toRmIds.forEach(topic2Id::remove); + LOGGER.info("contract " + contract + " unsubscribes topic " + topic); + } + return true; + } + + /** + * parse consumer information from content str
+ * if caller wants to select all consumers of a contract, the content str is also parsed into a node consumer + * + * @param content json string, {"subscriber": "[subscriber]", "handler?": "[handler]"} + * @return a node consumer or contract consumer, or null if exception is thrown + */ + public IEventConsumer parseConsumer(String content) { + if (null == content || content.isEmpty()) { + return null; + } + try { + JsonObject json = JsonUtil.parseString(content); + String subscriber = json.get("subscriber").getAsString(); + String handler = json.has("handler") ? json.get("handler").getAsString() : null; + if (null == subscriber || subscriber.isEmpty()) { + return null; + } + IEventConsumer consumer; + if (null != handler && !handler.isEmpty()) { + consumer = new ContractConsumer(subscriber, handler); + } else { + consumer = new NodeConsumer(subscriber); + } + return consumer; + } catch (Exception ignored) { + return null; + } + } + + // handle publishing; the process varies with the semantic of the event + private void doPublish(REvent event) { + String topic = event.getTopic(); + // publish simple event message to contract consumer + String cEventStr = JsonUtil.toJson(new Event(topic, event.getContent(), event.getSemantics())); + // publish full event message to node consumer + String nEventStr = JsonUtil.toJson(event); + if (!topic2cIds.containsKey(topic)) { + return; + } + Set topicConsumers = topic2cIds.get(topic); + switch (event.getSemantics()) { + case AT_LEAST_ONCE: + case NEED_RETRY: + // send events to all + topicConsumers.forEach(cId -> + deliverEvent(event, cEventStr, nEventStr, cId, topic, true)); + break; + case AT_MOST_ONCE: + // send event to a random consumer + // AT_MOST_ONCE, so broker don't need to do anything when delivering fails + deliverEvent( + event, + cEventStr, + nEventStr, + topicConsumers.toArray()[(int) (Math.random() * topicConsumers.size())].toString(), + topic, + true); + break; + case ONLY_ONCE: + switch (event.getType()) { + case PRESUB: + // receive PRESUB events and deliver the first one to the thread + String[] contentArr = event.getContent().split("\\|"); + if (contentArr.length == 3) { + ThreadFlag topicFlag; + synchronized (topicFlag = threadFlags.get(contentArr[1])) { + if (topicFlag.get().isEmpty()) { + topicFlag.set(event.getContent()); + topicFlag.notify(); + } + } + } + break; + case PUBLISH: + // the ONLY_ONCE event won't be delivered to the non-center + String contentHash = HashUtil.sha3(event.getContent()); + if (!threadFlags.containsKey(contentHash)) { + final ThreadFlag flag = new ThreadFlag(); + threadFlags.put(contentHash, flag); + // send PREPUB events to all consumers + // TODO if there are no consumers to receive the ONLY_ONCE events? + ContractManager.threadPool.execute(() -> { + REvent prePubMsg = new REvent(event.getTopic(), + PREPUB, + contentHash, + event.getRequestID()); + prePubMsg.doSignature(ContractManager.instance.nodeCenterConn.getNodeKeyPair()); + topicConsumers.forEach(cId -> + deliverEvent(prePubMsg, + JsonUtil.toJson( + new Event(event.getTopic(), + contentHash, + prePubMsg.getSemantics())), + JsonUtil.toJson(prePubMsg), + cId, + topic, + false)); + // wait for responses from contracts (PRESUB events) + while (true) { + try { + synchronized (flag) { + flag.wait(30 * 1000L); + } + if (!flag.get().isEmpty()) { + REvent finalMsg = new REvent(flag.get(), + PUBLISH, + event.getContent(), + HashUtil.sha3( + contentHash + System.currentTimeMillis())); + // if the delivering fails, retry publishing + finalMsg.setSemantics(NEED_RETRY); + finalMsg.doSignature( + ContractManager.instance.nodeCenterConn.getNodeKeyPair()); + handle(finalMsg); + break; + } + } catch (InterruptedException e) { + LOGGER.warn("ONLY_ONE event delivering is interrupted: " + e.getMessage()); +// e.printStackTrace(); + } + } + }); + } + default: + break; + } + + default: + break; + } + } + + /** + * publish the event to the consumer + * + * @param event event message + * @param cEventStr simple event message to the contract consumer, only the topic and the content + * @param nEventStr event message to the node + * @param cId consumer id + * @param topic topic of the event + * @param isPub if the event is published or pre-published + */ + private void deliverEvent( + REvent event, + String cEventStr, + String nEventStr, + String cId, + String topic, + boolean isPub) { + if (id2Consumers.containsKey(cId)) { + IEventConsumer consumer = id2Consumers.get(cId); + if (consumer instanceof ContractConsumer) { + // contract consumer + ContractManager.threadPool.execute(() -> { + ResultCallback cb = new ResultCallback() { + @Override + public void onResult(String str) { + // if the delivering fails, unsubscribe the consumer + if (null != str) { + ContractConsumer c = (ContractConsumer) consumer; + ContractClient client = ContractManager.instance.getClient(c.getContract()); + String reqID = + ContractManager.instance.nodeCenterConn.getNodeKeyPair().getPublicKeyStr() + + "_" + System.currentTimeMillis(); + REvent unsubEvent = + new REvent( + topic, + UNSUBSCRIBE, + "{\"subscriber\":\"" + cId + "\"}", + reqID); + unsubEvent.doSignature(client.getPubkey(), client.getContractKey()); + handle(unsubEvent); + + // if the event is an ONLY_ONCE event, retry publishing + if (NEED_RETRY.equals(event.getSemantics())) { + REvent newMsg = + new REvent( + topic.split("\\|")[0], + PUBLISH, + event.getContent(), + event.getRequestID()); + newMsg.setSemantics(ONLY_ONCE); + newMsg.setHash(event.getHash()); + newMsg.setTxHash(event.getTxHash()); + newMsg.doSignature(ContractManager.instance.nodeCenterConn.getNodeKeyPair()); + handle(newMsg); + } + } + } + }; + if (isPub) { + consumer.publishEvent(cEventStr, cb, event.getRequestID(), event.getHash()); + } else { + consumer.competeSub(cEventStr, cb, event.getRequestID(), event.getHash()); + } + }); + } else { + // node consumer + ContractManager.threadPool.execute(() -> { + if (isPub) { + consumer.publishEvent(nEventStr, null); + } else { + consumer.competeSub(nEventStr, null); + } + }); + } + } else { + topic2cIds.get(topic).remove(cId); + } + } + + /** + * return the number of topics, for node center statistics + * + * @return the number of topics + */ + public int countEvents() { + return topic2cIds.size() - tempTopics.size(); + } + + /** + * thread flag, used in ONLY_ONCE event publishing + */ + static class ThreadFlag { + private String flag = ""; + + public String get() { + return flag; + } + + public void set(String flag) { + this.flag = flag; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/bdware/sc/event/EventCenter.java b/src/main/java/org/bdware/sc/event/EventCenter.java new file mode 100644 index 0000000..f64b202 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/EventCenter.java @@ -0,0 +1,161 @@ +package org.bdware.sc.event; + +import org.bdware.sc.NodeCenterConn; +import org.bdware.sc.NodeCenterConn.NodeKey; +import org.bdware.sc.util.HashUtil; +import org.bdware.sc.util.JsonUtil; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.bdware.sc.ContractManager.instance; + +/** + * @author Kaidong Wu + */ +public class EventCenter { +// private static final Logger LOGGER = LogManager.getLogger(EventCenter.class); + + /** + * get the nearest node to the topic in the hash function range + * + * @param topic the topic + * @return id of the node + */ + public String getCenterByTopic(String topic) { + String[] centers = getCentersByTopic(topic, 1); + if (null == centers) { + return null; + } + return centers[0]; + } + + /** + * get k nearest nodes to the topic in the hash function range + * + * @param topic the topic + * @param k the number of required node ids + * @return ids of k nearest nodes + */ + public String[] getCentersByTopic(String topic, int k) { + NodeCenterConn nodeCenterConn = instance.nodeCenterConn; + if (null == nodeCenterConn) { + return null; + } + NodeKey[] nodes = nodeCenterConn.listNodes(); + if (nodes.length == 0) { + return null; + } + if (nodes.length == 1) { + return new String[]{nodeCenterConn.getNodeId(nodes[0].id)}; + } + + // get hash with enough length + String hash = HashUtil.sha3ToFixedLen(topic, nodes[0].id.length()); + BigInteger biH = new BigInteger(hash, 16); + + // binary search, to find the nearest node with hash + int l = 0, r = nodes.length - 1, m = 0, + comL = biH.compareTo(nodes[l].biId), comR = nodes[r].biId.compareTo(biH), + comM; + String selected; + do { + if (comL < 1) { + selected = nodes[l].id; + break; + } + if (comR < 1) { + selected = nodes[r].id; + break; + } + if (l + 1 == r) { + if (biH.subtract(nodes[l].biId).compareTo(nodes[r].biId.subtract(biH)) < 1) { + selected = nodes[l].id; + } else { + selected = nodes[r].id; + } + break; + } + m = (l + r) >> 1; + comM = biH.compareTo(nodes[m].biId); + if (comM < 1) { + r = m; + comR = -comM; + } else { + l = m; + comL = comM; + } + } while (true); + List ret = new ArrayList<>(); + ret.add(nodeCenterConn.getNodeId(selected)); + if (k > 1) { + l = m - 1; + r = m + 1; + while (ret.size() < k && (l >= 0 || r < nodes.length)) { + if (l < 0) { + ret.add(nodeCenterConn.getNodeId(nodes[r++].id)); + } else if (r >= nodes.length) { + ret.add(nodeCenterConn.getNodeId(nodes[l--].id)); + } else { + if (biH.subtract(nodes[l].biId).compareTo(nodes[r].biId.subtract(biH)) < 1) { + ret.add(nodeCenterConn.getNodeId(nodes[l--].id)); + } else { + ret.add(nodeCenterConn.getNodeId(nodes[r++].id)); + } + } + } + } + + return ret.toArray(new String[0]); + } + + /** + * subscribe a topic in center + * + * @param topic event topic + * @param semantics event semantics, used to mark PRESUB events + */ + public void subInCenter(String topic, REvent.REventSemantics semantics) { + if (null == instance.nodeCenterConn) { + return; + } + REvent msg = new REvent( + topic, + REvent.REventType.SUBSCRIBE, + String.format("{\"subscriber\":\"%s\"}", + instance.nodeCenterConn.getNodeId(null)), + ""); + msg.setSemantics(semantics); + msg.doSignature(instance.nodeCenterConn.getNodeKeyPair()); + String nodeId = getCenterByTopic(topic); + instance.nodeCenterConn.deliverEvent(JsonUtil.toJson(msg), nodeId); + } + + /** + * deliver an event to center + * + * @param topic event topic + * @param event the event + * @return if this node is the center, return false; otherwise, return true + */ + public boolean deliverEvent(String topic, REvent event) { + if (null == instance.nodeCenterConn) { + return false; + } + String nodeId = getCenterByTopic(topic); + return instance.nodeCenterConn.deliverEvent(JsonUtil.toJson(event), nodeId); + } + + /** + * publish an event to another node; used by NodeConsumer + * + * @param eStr event string + * @param target id of the target node + */ + public void publishEvent(String eStr, String target) { + if (null != instance.nodeCenterConn) { + instance.nodeCenterConn.deliverEvent(eStr, target); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/bdware/sc/event/EventRecorder.java b/src/main/java/org/bdware/sc/event/EventRecorder.java new file mode 100644 index 0000000..7d1f2f7 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/EventRecorder.java @@ -0,0 +1,237 @@ +package org.bdware.sc.event; + +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.db.KeyValueRocksDBUtil; +import org.bdware.sc.event.REvent.REventSemantics; +import org.bdware.sc.event.REvent.REventType; +import org.bdware.sc.event.clients.ContractConsumer; +import org.bdware.sc.event.clients.IEventConsumer; +import org.bdware.sc.event.clients.NodeConsumer; +import org.bdware.sc.util.HashUtil; +import org.bdware.sc.util.JsonUtil; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; + +import static org.bdware.sc.event.REvent.REventType.SUBSCRIBE; +import static org.bdware.sc.event.REvent.REventType.UNSUBSCRIBE; + +/** + * @author Kaidong Wu + */ +public class EventRecorder { + private static final Logger LOGGER = LogManager.getLogger(EventRecorder.class); + private static final String LATEST_EVENT_KEY = "LATEST_EVENT", TEMP_TOPIC_KEYS = "TEMP_TOPICS"; + + private final String dbName; + private final EventBroker broker; + private final RecordPointer latestEvent = new RecordPointer(); + + public EventRecorder(String dbName, EventBroker broker) { + this.dbName = dbName; + this.broker = broker; + } + + public void appendEvent(REvent e) { + ETransaction transaction = new ETransaction(e); + synchronized (latestEvent) { + transaction.prev = latestEvent.get(); + String tranJson = "et" + JsonUtil.toJson(transaction); + String key = HashUtil.sha3(tranJson); + latestEvent.set(key); + latestEvent.setCp(false); + KeyValueRocksDBUtil.instance.setValue(dbName, LATEST_EVENT_KEY, latestEvent.get()); + KeyValueRocksDBUtil.instance.setValue(dbName, key, tranJson); + } + } + + public void createCheckPoint(Map> topic2cIds, + Map id2Consumers) { + CheckPoint cp = new CheckPoint(topic2cIds, id2Consumers); + synchronized (latestEvent) { + if (!latestEvent.isCp()) { + cp.prev = latestEvent.get(); + String cpJson = "cp" + JsonUtil.toJson(cp); + String key = HashUtil.sha3(cpJson); + latestEvent.set(key); + latestEvent.setCp(true); + KeyValueRocksDBUtil.instance.setValue(dbName, LATEST_EVENT_KEY, latestEvent.get()); + KeyValueRocksDBUtil.instance.setValue(dbName, key, cpJson); + } + } + } + + /** + * recover registry from database + * + * @return a check point, including topic registry and client registry + */ + public CheckPoint recoverRegistryFromDb() { + Stack stack = new Stack<>(); + // recover the latest event pointer from database + String key = KeyValueRocksDBUtil.instance.getValue(dbName, LATEST_EVENT_KEY); + latestEvent.set(key); + CheckPoint cp = new CheckPoint(); + Type topic2cIdsType = TypeToken.getParameterized(ConcurrentHashMap.class, String.class, + TypeToken.getParameterized(Set.class, String.class, + String.class).getType()).getType(); + // retrieving transactions from database + while (null != key && !key.isEmpty()) { + String json = KeyValueRocksDBUtil.instance.getValue(dbName, key); + if (null == json || json.isEmpty()) { + LOGGER.warn("record damaged! " + key); + break; + } + try { + if (json.startsWith("cp")) { + // create check point by the transaction and stop retrieving + JsonObject data = JsonUtil.parseString(json.substring(2)); + cp.topic2cIds = JsonUtil.fromJson(data.get("topic2cIds").toString(), topic2cIdsType); + JsonObject id2Consumers = data.getAsJsonObject("id2Consumers"); + for (String k : id2Consumers.keySet()) { + JsonObject consumer = id2Consumers.getAsJsonObject(k); + if (consumer.has("handler")) { + cp.id2Consumers.put(k, + new ContractConsumer(consumer.get("contract").getAsString(), + consumer.get("handler").getAsString())); + } else { + cp.id2Consumers.put(k, new NodeConsumer(k)); + } + } + cp.prev = data.get("prev").getAsString(); + break; + } else if (json.startsWith("et")) { + // push transactions into the stack + ETransaction tran = JsonUtil.fromJson(json.substring(2), ETransaction.class); + key = tran.prev; + // just sub or unsub event need to be processed + if (tran.type == SUBSCRIBE || tran.type == UNSUBSCRIBE) { + stack.push(tran); + } + } else { + LOGGER.warn("record damaged! " + key); + break; + } + } catch (Exception ignored) { + LOGGER.warn("record damaged! " + key); + LOGGER.debug("record damaged! " + key + ": " + json); + break; + } + } + if (stack.isEmpty()) { + // if empty, return the check point + latestEvent.setCp(true); + } else { + // on the base of old check point, process following sub or unsub events to recover registry + while (!stack.empty()) { + Object record = stack.pop(); + if (record instanceof CheckPoint) { + cp = (CheckPoint) record; + } else { + ETransaction tran = (ETransaction) record; + IEventConsumer consumer = broker.parseConsumer(tran.content); + switch (tran.type) { + case SUBSCRIBE: + if (!broker.subInReg(tran.topic, consumer, cp.topic2cIds, cp.id2Consumers)) { + LOGGER.warn("record damaged! " + key); + LOGGER.debug("record damaged! " + key + ": " + JsonUtil.toJson(tran)); + } + break; + case UNSUBSCRIBE: + if (!broker.unsubInReg(tran.topic, consumer, cp.topic2cIds, cp.id2Consumers)) { + LOGGER.warn("record damaged! " + key); + LOGGER.debug("record damaged! " + key + ": " + JsonUtil.toJson(tran)); + } + default: + break; + } + } + } + } + LOGGER.info("recover registry from database done!"); + return cp; + } + + public void saveTempTopics(Map tempTopics) { + KeyValueRocksDBUtil.instance.setValue(dbName, TEMP_TOPIC_KEYS, JsonUtil.toJson(tempTopics)); + } + + public Map recoverTempTopicsFromDb() { + String json = KeyValueRocksDBUtil.instance.getValue(dbName, TEMP_TOPIC_KEYS); + Map ret = new ConcurrentHashMap<>(); + try { + JsonObject jo = JsonUtil.parseString(json); + for (String key : jo.keySet()) { + ret.put(key, jo.get(key).getAsLong()); + } + } catch (Exception ignored) { + } + return ret; + } + + static class ETransaction { + REventType type; + REventSemantics semantics; + String topic; + String content; + String requestID; + // public key of sender + String sender; + String signature; + String prev; + + public ETransaction(REvent e) { + type = e.getType(); + semantics = e.getSemantics(); + topic = e.getTopic(); + content = e.getContent(); + requestID = e.getRequestID(); + sender = e.getPublicKey(); + signature = e.getSignature(); + } + } + + static class CheckPoint { + Map> topic2cIds; + Map id2Consumers; + String prev; + + public CheckPoint() { + topic2cIds = new ConcurrentHashMap<>(); + id2Consumers = new ConcurrentHashMap<>(); + } + + public CheckPoint(Map> topic2cIds, + Map id2Consumers) { + this.topic2cIds = topic2cIds; + this.id2Consumers = id2Consumers; + } + } + + static class RecordPointer { + private String str; + private boolean isCp = false; + + public String get() { + return str; + } + + public void set(String str) { + this.str = str; + } + + public boolean isCp() { + return isCp; + } + + public void setCp(boolean cp) { + isCp = cp; + } + } +} diff --git a/src/main/java/org/bdware/sc/event/clients/ClientConsumer.java b/src/main/java/org/bdware/sc/event/clients/ClientConsumer.java new file mode 100644 index 0000000..7d46998 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/clients/ClientConsumer.java @@ -0,0 +1,23 @@ +package org.bdware.sc.event.clients; + +import org.bdware.sc.conn.ResultCallback; + +/** + * @author Kaidong Wu + */ +public class ClientConsumer implements IEventConsumer { + @Override + public String getId() { + return null; + } + + @Override + public void publishEvent(String msg, ResultCallback rc, String... options) { +// TODO + } + + @Override + public void competeSub(String msg, ResultCallback rc, String... options) { +// TODO + } +} diff --git a/src/main/java/org/bdware/sc/event/clients/ContractConsumer.java b/src/main/java/org/bdware/sc/event/clients/ContractConsumer.java new file mode 100644 index 0000000..9d51d31 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/clients/ContractConsumer.java @@ -0,0 +1,119 @@ +package org.bdware.sc.event.clients; + +import com.google.gson.annotations.Expose; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.ContractClient; +import org.bdware.sc.ContractManager; +import org.bdware.sc.ContractResult; +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.util.HashUtil; +import org.bdware.sc.util.JsonUtil; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Kaidong Wu + */ +public class ContractConsumer implements IEventConsumer { + private static final Logger LOGGER = LogManager.getLogger(ContractConsumer.class); + private static final long PERIOD = 2500L; + private static final int TIMEOUT_COUNT = 5; + private static final Map> scheduledFutures = + new ConcurrentHashMap<>(); + + private final String contract; + private final String handler; + @Expose(serialize = false, deserialize = false) + private final Object flag = new Object(); + + public ContractConsumer(String contract, String handler) { + this.contract = contract; + this.handler = handler; + } + + public String getContract() { + return contract; + } + + @Override + public String getId() { + return HashUtil.sha3(contract + handler); + } + + @Override + public void publishEvent(String msg, ResultCallback rc, String... options) { + executeContract(msg, this.handler, rc, options); + } + + @Override + public void competeSub(String msg, ResultCallback rc, String... options) { + executeContract(msg, "_preSub", rc, options); + } + + private void executeContract(String msg, String handler, ResultCallback rc, String... options) { + ContractRequest cr = new ContractRequest(); + if (options.length > 1 && null != options[1]) { + cr.setRequester(options[1]); + } else { + cr.setRequester("event"); + } + cr.setContractID(contract); + cr.setAction(handler); + cr.setArg(msg); + cr.setRequestID(options[0]); + ContractClient cc = ContractManager.instance.getClient(contract); + if (null == cc) { + rc.onResult(""); + return; + } + AtomicInteger callCount = new AtomicInteger(0); + ScheduledFuture future = ContractManager.scheduledThreadPool.scheduleAtFixedRate( + () -> ContractManager.instance.executeContractInternal(cr, new ResultCallback() { + @Override + public void onResult(String str) { + boolean ret = true; + try { + ContractResult result = JsonUtil.fromJson(str, ContractResult.class); + if (result.status == ContractResult.Status.Success) { + rc.onResult(null); + } else if (callCount.get() == TIMEOUT_COUNT || + (result.status == ContractResult.Status.Exception && + result.result.toString().contains("not exported"))) { + rc.onResult(""); + } else { + ret = false; + } + } catch (Exception e) { + LOGGER.warn("receiving event error! " + contract + "." + handler + ": " + e.getMessage()); + rc.onResult(""); + } + callCount.incrementAndGet(); + if (ret) { + synchronized (flag) { + try { + // wait for setting scheduledFutures + flag.wait(500L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + scheduledFutures.get(getId()).cancel(true); + } + } + }, (reqID, hashStr) -> { + }), + 500L, + PERIOD, + TimeUnit.MILLISECONDS); + scheduledFutures.put(getId(), future); + synchronized (flag) { + flag.notify(); + } + } +} diff --git a/src/main/java/org/bdware/sc/event/clients/IEventConsumer.java b/src/main/java/org/bdware/sc/event/clients/IEventConsumer.java new file mode 100644 index 0000000..20a607d --- /dev/null +++ b/src/main/java/org/bdware/sc/event/clients/IEventConsumer.java @@ -0,0 +1,11 @@ +package org.bdware.sc.event.clients; + +import org.bdware.sc.conn.ResultCallback; + +public interface IEventConsumer { + String getId(); + + void publishEvent(String msg, ResultCallback rc, String... options); + + void competeSub(String msg, ResultCallback rc, String... options); +} diff --git a/src/main/java/org/bdware/sc/event/clients/NodeConsumer.java b/src/main/java/org/bdware/sc/event/clients/NodeConsumer.java new file mode 100644 index 0000000..ca08a41 --- /dev/null +++ b/src/main/java/org/bdware/sc/event/clients/NodeConsumer.java @@ -0,0 +1,39 @@ +package org.bdware.sc.event.clients; + +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.event.EventCenter; + +/** + * @author Kaidong Wu + */ +public class NodeConsumer implements IEventConsumer { + private static EventCenter center; + + private final String nodeId; + + public NodeConsumer(String nodeId) { + this.nodeId = nodeId; + } + + public static void setCenter(EventCenter center) { + NodeConsumer.center = center; + } + + @Override + public String getId() { + return this.nodeId; + } + + @Override + public void publishEvent(String msg, ResultCallback rc, String... options) { + center.publishEvent(msg, nodeId); + if (null != rc) { + rc.onResult(""); + } + } + + @Override + public void competeSub(String msg, ResultCallback rc, String... options) { + publishEvent(msg, rc, options); + } +} \ No newline at end of file diff --git a/src/main/java/org/bdware/sc/handler/ManagerHandler.java b/src/main/java/org/bdware/sc/handler/ManagerHandler.java new file mode 100644 index 0000000..52236ad --- /dev/null +++ b/src/main/java/org/bdware/sc/handler/ManagerHandler.java @@ -0,0 +1,101 @@ +package org.bdware.sc.handler; + +import org.bdware.sc.ContractManager; +import org.bdware.sc.bean.Contract; +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.Description; +import org.bdware.sc.conn.MsgHandler; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.event.REvent; +import org.bdware.sc.get.GetMessage; +import org.bdware.sc.util.JsonUtil; + +public class ManagerHandler extends MsgHandler { + + private final ContractManager cm; + + public ManagerHandler(ContractManager contractManager) { + cm = contractManager; + } + + @Description(value = "start Contract, {\"type\":\"Data\",\"id\":\"123bbaa\"}", isAsync = true) + public void startContract(GetMessage msg, ResultCallback cb) { + Contract c = JsonUtil.fromJson(msg.arg, Contract.class); + cb.onResult(cm.startContract(c)); + } + + @Description("request Contract, TODO") + public void requestContract(GetMessage msg, ResultCallback cb) { + ContractRequest c = JsonUtil.fromJson(msg.arg, ContractRequest.class); + cb.onResult(cm.requestContract(c)); + } + + @Description("get how many times a contract has been executed") + public void getTimesOfExecution(GetMessage msg, ResultCallback cb) { + cb.onResult(cm.getTimesOfExecution(msg.arg)); + } + + @Description( + value = "execute Contract, {\"contractID\":\"112233\",\"arg\":\"\"}", + isAsync = true) + public void executeContract(GetMessage msg, ResultCallback cb) { + ContractRequest c; + try { + c = JsonUtil.fromJson(msg.arg, ContractRequest.class); + cm.executeContractInternal(c, cb, null); + } catch (Exception ignored) { + } + } + + + @Description("stop Contract, {\"id\":\"112233\"}") + public void stopContract(GetMessage msg, ResultCallback cb) { + ContractRequest c = JsonUtil.fromJson(msg.arg, ContractRequest.class); + cb.onResult(cm.stopContract(c.getContractID())); + } + + @Description("list Contracts") + public void listContracts(GetMessage msg, ResultCallback cb) { + cb.onResult(cm.listContracts(null)); + } + + @Description("stop all Contracts") + public void stopAllContracts(GetMessage msg, ResultCallback cb) { + int count = cm.stopAllContracts(); + cb.onResult(count + " contracts stoped!"); + } + + @Description("exit ContractManager!") + public void exit(GetMessage msg, ResultCallback cb) { + ContractManager.threadPool.execute(() -> { + System.out.println("ManagerHandler: exit in 3 seconds!"); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.exit(0); + }); + cb.onResult("success"); + } + + @Description("query distributed execution port by contractID/name") + public void queryDEPort(GetMessage msg, ResultCallback cb) { + cb.onResult(cm.queryDEPort(msg.arg)); + } + + @Description("add distributed execution member, sample: {contractID:id/name, arg:ipAndPort}") + public void addDEMember(GetMessage msg, ResultCallback cb) { + ContractRequest cr = JsonUtil.fromJson(msg.arg, ContractRequest.class); + cb.onResult(cm.addDEMember(cr.getContractID(), cr.getArg())); + } + + /** + * @author Kaidong Wu + */ + @Description("Deliver event message") + public void deliverEMessage(GetMessage msg, ResultCallback cb) { + REvent eMsg = JsonUtil.fromJson(msg.arg, REvent.class); + cb.onResult(cm.deliverEMessage(eMsg)); + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/Committer.java b/src/main/java/org/bdware/sc/sequencing/Committer.java new file mode 100644 index 0000000..a8394ec --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/Committer.java @@ -0,0 +1,7 @@ +package org.bdware.sc.sequencing; + +import org.bdware.sc.bean.ContractRequest; + +public interface Committer { + void onCommit(ContractRequest data); +} diff --git a/src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java b/src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java new file mode 100644 index 0000000..8a6076f --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java @@ -0,0 +1,382 @@ +package org.bdware.sc.sequencing; + +import org.bdware.sc.ContractManager; +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.Node; +import org.bdware.sc.units.TrustfulExecutorConnection; +import org.bdware.sc.util.JsonUtil; +import org.zz.gmhelper.SM2KeyPair; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class PBFTAlgorithm implements SequencingAlgorithm { + static byte[] GETPUBKEY = "GETPUBKEY".getBytes(); + Committer committer; + Map members; + AtomicLong allocatedID = new AtomicLong(0); + Map info; + List commitedMsg; + AtomicLong commitedOrder = new AtomicLong(0); + Map> original; + boolean isMaster; + private TrustfulExecutorConnection connection; + + public PBFTAlgorithm(boolean isMaster) { + commitedMsg = new ArrayList<>(); + info = new HashMap<>(); + members = new HashMap<>(); + original = new HashMap<>(); + this.isMaster = isMaster; + } + + public void setCommitter(Committer c) { + committer = c; + } + + public void setConnection(TrustfulExecutorConnection c) { + connection = c; + } + + private void onPBFTMessage(final Node sender, final PBFTMessage pbftMessage) { + // pbftMessage.type != Request + // verify signed message + // System.out.println(getPort() + " recv:" + pbftMessage.type + " len:" + + // pbftMessage.content.length); + PCInfo temp; + PBFTMessage prepareMsg; + System.out.println("[PBFTAlgorithm] recv: " + pbftMessage.getDisplayStr()); + switch (pbftMessage.type) { + case AddMember: + PBFTMember member = PBFTMember.parse(pbftMessage.content); + if (member != null) + members.put(sender, member); + // case DeleteMember: + break; + case Request: + // all nodes save full content, clientIP, clientPort !!!! + int hash = java.util.Arrays.hashCode(pbftMessage.content); + original.put(hash, new Pair(sender, pbftMessage)); + if (isMaster) { + prepareMsg = new PBFTMessage(); + prepareMsg.order = allocatedID.incrementAndGet(); + prepareMsg.type = PBFTType.PrePrepare; + prepareMsg.content = (java.util.Arrays.hashCode(pbftMessage.content) + "").getBytes(); + + temp = new PCInfo(); + temp.request = pbftMessage; + if (info.get(prepareMsg.order) != null) { + System.out.println("==========================Error!!!"); + } + info.put(prepareMsg.order, temp); + temp.isPrePrepareReceived = true; + broadcast(prepareMsg); + } else { + // System.out.println(getPort() + " Request:" + pbftMessage.getDisplayStr()); + matchPrePrepareFromOriginReqeust(hash, pbftMessage); + } + reply(sender, "success"); + break; + case PrePrepare: + // assert !isMaster + // Get Pubkey of Master + // verify the content matches full content !!!! and client info + if (info.get(pbftMessage.order) == null) { + info.put(pbftMessage.order, new PCInfo()); + } + matchOriginalReqeustFromPrePrepare(pbftMessage); + updatePrepare(info.get(pbftMessage.order)); + // TODO 触发prepare + break; + case Prepare: + temp = info.get(pbftMessage.order); + // System.out.println(getPort() + ": receive Prepare from:" + packet.getPort()); + if (temp == null) { + PCInfo pcInfo = new PCInfo(); + pcInfo.buff.add(pbftMessage); + info.put(pbftMessage.order, pcInfo); + requetPrePrepareFromMaster(pbftMessage.order); + } else if (temp.updatePrepare(pbftMessage, this)) { + // check the sender (is master) and order is in range !!!! + PBFTMessage commitMsg = new PBFTMessage(); + commitMsg.type = PBFTType.Commit; + commitMsg.order = pbftMessage.order; + commitMsg.content = new byte[1]; + updateCommit(commitMsg); + broadcast(commitMsg); + } + break; + case Commit: + temp = info.get(pbftMessage.order); + if (temp == null) { + PCInfo pcInfo = new PCInfo(); + pcInfo.buff.add(pbftMessage); + info.put(pbftMessage.order, pcInfo); + requetPrePrepareFromMaster(pbftMessage.order); + break; + } + if (temp.updateCommit(pbftMessage, this)) { + // check all messages' order !!!! + // write order to content !!!! + PBFTMessage commitMsg = new PBFTMessage(); + commitMsg.type = PBFTType.Reply; + // execute in order + onCommit(pbftMessage);// once complete automatically send to client !!!! + // broadCast(commitMsg); + } + break; + case Reply:// client receive this and check f+1 same result + if (isMaster) { + System.out.println(pbftMessage.order + " " + pbftMessage.sendID); + } + break; + case ReSend: + // TODO + if (isMaster) { + prepareMsg = new PBFTMessage(); + prepareMsg.order = pbftMessage.order; + prepareMsg.type = PBFTType.PrePrepare; + temp = info.get(prepareMsg.order); + prepareMsg.content = (java.util.Arrays.hashCode(temp.request.content) + "").getBytes(); + broadcast(pbftMessage); + } + default: + } + + // printDebugInfo(); + + } + + private void updateCommit(PBFTMessage commitMsg) { + PCInfo temp = info.get(commitMsg.order); + if (temp.updateCommit(commitMsg, this)) { + // check all messages' order !!!! + // write order to content !!!! + PBFTMessage replyMsg = new PBFTMessage(); + commitMsg.type = PBFTType.Reply; + // execute in order + onCommit(commitMsg);// once complete automatically send to client !!!! + // broadCast(commitMsg); + } + + } + + private void updatePrepare(PCInfo pcInfo) { + for (PBFTMessage msg : pcInfo.buff) { + if (msg.type == PBFTType.Prepare) { + if (pcInfo.updatePrepare(msg, this)) { + PBFTMessage commitMsg = new PBFTMessage(); + commitMsg.type = PBFTType.Commit; + commitMsg.order = msg.order; + commitMsg.content = new byte[1]; + updateCommit(commitMsg); + broadcast(commitMsg); + } + } + } + } + + private void requetPrePrepareFromMaster(long order) { + PBFTMessage message = new PBFTMessage(); + message.type = PBFTType.ReSend; + message.order = order; + message.content = new byte[1]; + sendToMaster(message); + } + + private void matchPrePrepareFromOriginReqeust(int hash, PBFTMessage pbftMessage) { + for (PCInfo pcInfo : info.values()) { + for (PBFTMessage msg : pcInfo.buff) { + if (msg.type == PBFTType.PrePrepare && Integer.parseInt(new String(msg.content)) == hash) { + handlePrePrepare(msg, original.get(hash).second); + return; + } + } + } + } + + private void reply(Node receiver, String data) { + + } + + private void retryLater(int delay, final Node sender, final PBFTMessage pbftMessage) { + ContractManager.scheduledThreadPool.schedule( + () -> onPBFTMessage(sender, pbftMessage), + delay, + TimeUnit.MILLISECONDS); + } + + private void handlePrePrepare(PBFTMessage pbftMessage, PBFTMessage req) { + PBFTMessage prepareMsg = new PBFTMessage(); + prepareMsg.type = PBFTType.Prepare; + prepareMsg.order = pbftMessage.order; + prepareMsg.content = pbftMessage.content; + PCInfo temp; + if (info.get(prepareMsg.order) == null) { + temp = new PCInfo(); + info.put(prepareMsg.order, temp); + } else + temp = info.get(prepareMsg.order); + temp.request = req; + temp.isPrePrepareReceived = true; + temp.updatePrepare(prepareMsg, this); + // System.out.println(getPort() + ":sendPrepare " + pbftMessage.order); + broadcast(prepareMsg); + + } + + private void matchOriginalReqeustFromPrePrepare(PBFTMessage pbftMessage) { + int receivedHash = Integer.parseInt(new String(pbftMessage.content)); + if (original.get(receivedHash) != null) { + handlePrePrepare(pbftMessage, original.get(receivedHash).second); + } else { + info.get(pbftMessage.order).buff.add(pbftMessage); + } + } + + private synchronized void onCommit(PBFTMessage pbftMessage) { + PBFTMessage original = getOriginalMessage(pbftMessage.order); + // execute(pbftMessage); + if (pbftMessage.order == commitedOrder.get() + 1) { + execute(original); + } else { + commitedMsg.add(original); + } + } + + private synchronized void execute(PBFTMessage pbftMessage) { + commitedOrder.incrementAndGet(); + PBFTMessage original = getOriginalMessage(pbftMessage.order); + ContractRequest msg = ContractRequest.parse(original.content); + committer.onCommit(msg); + + System.out.println(": execute, " + JsonUtil.toJson(msg)); + info.remove(pbftMessage.order); + // ContractProcess.instance.onEvent(msg); + commitedMsg.sort((o1, o2) -> (int) (o1.order - o2.order)); + Set executedMsgs = new HashSet<>(); + for (PBFTMessage message : commitedMsg) { + if (commitedOrder.get() == message.order) { + commitedOrder.incrementAndGet(); + msg = ContractRequest.parse(getOriginalMessage(message.order).content); + committer.onCommit(msg); + executedMsgs.add(message); + // info.remove(pbftMessage.order); + } else + break; + } + commitedMsg.removeAll(executedMsgs); + + } + + private PBFTMessage getOriginalMessage(long order) { + return info.get(order).request; + } + + private void broadcast(PBFTMessage pbftMessage) { + for (Node node : members.keySet()) { + System.out.println("[PBFTAlgorithm] send: " + pbftMessage.getDisplayStr()); + + connection.sendMessage(node, pbftMessage.getBytes()); + } + } + + private void sendToMaster(PBFTMessage pbftMessage) { + for (Node node : members.keySet()) { + PBFTMember member = members.get(node); + if (member.isMaster) { + connection.sendMessage(node, pbftMessage.getBytes()); + return; + } + } + } + + public int getMemberSize() { + return members.size(); + } + + @Override + public void onMessage(Node node, byte[] msg) { + PBFTMessage pbftMessage = PBFTMessage.parse(msg); + onPBFTMessage(node, pbftMessage); + } + + static class NodeInfo { + SM2KeyPair privKey; + int hash; + boolean isMaster; + + public NodeInfo(SM2KeyPair key) { + privKey = key; + hash = Arrays.hashCode(privKey.getPublicKey().getQ().getEncoded(false)); + isMaster = false; + } + + public int getNodeID() { + return hash; + } + + public boolean isMaster() { + return isMaster; + } + + } + + static class PCInfo { + public PBFTMessage request; + Set prepare; + Set commit; + boolean isPrePrepareReceived; + boolean isSendCommit; + boolean isSendReply; + List buff; + + PCInfo() { + prepare = new HashSet<>(); + commit = new HashSet<>(); + buff = new ArrayList<>(); + isSendCommit = false; + isSendReply = false; + isPrePrepareReceived = false; + } + + public synchronized boolean updatePrepare(PBFTMessage message, PBFTAlgorithm center) { + if (isSendCommit) + return false; + prepare.add(message.sendID); + if (!isPrePrepareReceived) { + return false; + } +// System.out.println("---: updatePrepare, size:" + prepare.size() + " -->" +// + (center.members.size() / 3 * 2 + 1) + " --senderID:" + message.sendID); + + if (prepare.size() > center.members.size() / 3 * 2) { + isSendCommit = true; + return true; + } + return false; + } + + public synchronized boolean updateCommit(PBFTMessage message, PBFTAlgorithm center) { + if (isSendReply) + return false; + commit.add(message.sendID); + if (!isSendCommit) + return false; + if (commit.size() > center.members.size() / 3 * 2) { + prepare = null; + commit = null; + isSendReply = true; + return true; + } + return false; + } + + public String getDisplayStr() { + return "pSize:" + (prepare == null ? "null" : prepare.size()) + " cSize:" + + (commit == null ? "null" : commit.size()) + " isSendCommit:" + isSendCommit + " isSendReply:" + + isSendReply + " buffSize:" + buff.size(); + } + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/PBFTMember.java b/src/main/java/org/bdware/sc/sequencing/PBFTMember.java new file mode 100644 index 0000000..7ab0b44 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/PBFTMember.java @@ -0,0 +1,43 @@ +package org.bdware.sc.sequencing; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class PBFTMember implements Serializable { + + private static final long serialVersionUID = -3058672609865345062L; + public boolean isMaster; + + public boolean isMaster() { + return isMaster; + } + + public static PBFTMember parse(byte[] content) { + try { + ByteArrayInputStream input = new ByteArrayInputStream(content); + ObjectInputStream objectInputStream = new ObjectInputStream(input); + PBFTMember ret = (PBFTMember) objectInputStream.readObject(); + return ret; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public byte[] toByteArray() { + ObjectOutputStream out; + try { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + out = new ObjectOutputStream(bo); + out.writeObject(this); + return bo.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/PBFTMessage.java b/src/main/java/org/bdware/sc/sequencing/PBFTMessage.java new file mode 100644 index 0000000..9115fd5 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/PBFTMessage.java @@ -0,0 +1,99 @@ +package org.bdware.sc.sequencing; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.bdware.sc.conn.ByteUtil; + +public class PBFTMessage { + PBFTType type; + int sendID; + long order; + byte[] content; + String stamp;// time-stamp or other string, given by client, to identify the content + String signature; + + public int getBytesHash() { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + bo.write(type.toInt()); + ByteUtil.writeLong(bo, sendID); + ByteUtil.writeLong(bo, order); + try { + bo.write(content); + } catch (IOException e) { + e.printStackTrace(); + } + byte[] ret = bo.toByteArray(); + int hashCode = ret.hashCode(); + return hashCode; + } + + public byte[] getBytes() { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + try { + bo.write(type.toInt()); + ByteUtil.writeInt(bo, sendID); + ByteUtil.writeLong(bo, order); + if (signature != null) { + byte[] signature = this.signature.getBytes(); + ByteUtil.writeInt(bo, signature.length); + bo.write(signature); + } else + ByteUtil.writeInt(bo, 0); + + bo.write(content); + } catch (IOException e) { + e.printStackTrace(); + } + return bo.toByteArray(); + } + + public static PBFTMessage parse(byte[] b) { + PBFTMessage msg = new PBFTMessage(); + ByteArrayInputStream bi = new ByteArrayInputStream(b); + msg.type = PBFTType.fromByte(bi.read()); + msg.sendID = ByteUtil.readInt(bi); + msg.order = ByteUtil.readLong(bi); + int sigLen = ByteUtil.readInt(bi); + if (sigLen > 0) + msg.signature = new String(ByteUtil.readBytes(bi, sigLen)); + else + msg.signature = null; + msg.content = ByteUtil.readBytes(bi, b.length - 1 - 4 - 8 - 4 - sigLen); + return msg; + } + + private static String IDA = "PBFTMsg"; + +// public void sign(SM2KeyPair pair) { +// sendID = Arrays.hashCode(pair.getPublicKey().getEncoded(false)); +// signature = sm02.sign(content, IDA, pair).toString(); +// } +// +// public boolean verify(PBFTMember member) { +// return sm02.verify(content, Signature.loadFromString(signature), IDA, +// SM2KeyPair.publicKeyStr2ECPoint(member.pubKey)); +// } + + public void setType(PBFTType type) { + this.type = type; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public String getDisplayStr() { + StringBuilder sb = new StringBuilder(); + sb.append(sendID); + sb.append("_"); + sb.append(order); + sb.append("_TYPE_").append(type); + sb.append(" --> sig:"); + sb.append(signature); + sb.append(" content:"); + sb.append(new String(content)); + return sb.toString(); + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/PBFTType.java b/src/main/java/org/bdware/sc/sequencing/PBFTType.java new file mode 100644 index 0000000..b863b47 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/PBFTType.java @@ -0,0 +1,35 @@ +package org.bdware.sc.sequencing; + +public enum PBFTType { + Request(0), PrePrepare(1), Prepare(2), Commit(3), Reply(4), Unknown(5), ReSend(6), AddMember(7); + private int type; + + PBFTType(int i) { + type = i; + } + + public static PBFTType fromByte(int i) { + switch (i) { + case 0: + return Request; + case 1: + return PrePrepare; + case 2: + return Prepare; + case 3: + return Commit; + case 4: + return Reply; + case 6: + return ReSend; + case 7: + return AddMember; + default: + return Unknown; + } + } + + public int toInt() { + return type; + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/Pair.java b/src/main/java/org/bdware/sc/sequencing/Pair.java new file mode 100644 index 0000000..8bcb761 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/Pair.java @@ -0,0 +1,11 @@ +package org.bdware.sc.sequencing; + +public class Pair { + public T1 first; + public T2 second; + + public Pair(T1 t1, T2 t2) { + first = t1; + second = t2; + } +} diff --git a/src/main/java/org/bdware/sc/sequencing/SequencingAlgorithm.java b/src/main/java/org/bdware/sc/sequencing/SequencingAlgorithm.java new file mode 100644 index 0000000..32650d7 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/SequencingAlgorithm.java @@ -0,0 +1,12 @@ +package org.bdware.sc.sequencing; + +import org.bdware.sc.conn.Node; +import org.bdware.sc.units.TrustfulExecutorConnection; + +public interface SequencingAlgorithm { + void onMessage(Node node, byte[] msg); + + void setCommitter(Committer c); + + void setConnection(TrustfulExecutorConnection c); +} \ No newline at end of file diff --git a/src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java b/src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java new file mode 100644 index 0000000..06f1488 --- /dev/null +++ b/src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java @@ -0,0 +1,31 @@ +package org.bdware.sc.sequencing; + +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.Node; +import org.bdware.sc.units.TrustfulExecutorConnection; + +public class ViewAlgorithm implements SequencingAlgorithm { + + private Committer committer; + private TrustfulExecutorConnection connection; + + public ViewAlgorithm(boolean isMaster) { + } + + public void setCommitter(Committer c) { + committer = c; + } + + public void setConnection(TrustfulExecutorConnection c) { + connection = c; + } + + @Override + public void onMessage(Node node, byte[] msg) { + PBFTMessage pbftMessage = PBFTMessage.parse(msg); + if (pbftMessage.type == PBFTType.Request) { + ContractRequest cr = ContractRequest.parse(pbftMessage.content); + committer.onCommit(cr); + } + } +} diff --git a/src/main/java/org/bdware/sc/units/ByteUtil.java b/src/main/java/org/bdware/sc/units/ByteUtil.java new file mode 100644 index 0000000..85b0538 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ByteUtil.java @@ -0,0 +1,53 @@ +package org.bdware.sc.units; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +public class ByteUtil { +// public static byte[] readRest(ByteArrayInputStream bi) { +// byte[] ret = new byte[bi.available()]; +// bi.read(ret, 0, ret.length); +// return ret; +// } + + public static int readInt(ByteArrayInputStream bi) { + int ret = 0; + ret |= (bi.read() & 0xff); + ret <<= 8; + ret |= (bi.read() & 0xff); + ret <<= 8; + ret |= (bi.read() & 0xff); + ret <<= 8; + ret |= (bi.read() & 0xff); + return ret; + } + + public static void writeInt(ByteArrayOutputStream bo, int i) { + bo.write((i >> 24) & 0xff); + bo.write((i >> 16) & 0xff); + bo.write((i >> 8) & 0xff); + bo.write(i & 0xff); + + } + + public static void writeLong(ByteArrayOutputStream bo, long l) { + for (int i = 56; i >= 0; i -= 8) { + bo.write(((int) (l >> i)) & 0xff); + } + } + + public static long readLong(ByteArrayInputStream bi) { + long ret = 0L; + for (int i = 0; i < 8; i++) { + ret <<= 8; + ret |= (bi.read() & 0xff); + } + return ret; + } + + public static byte[] readBytes(ByteArrayInputStream bi, int len) { + byte[] ret = new byte[len]; + bi.read(ret, 0, len); + return ret; + } +} diff --git a/src/main/java/org/bdware/sc/units/ContractRecord.java b/src/main/java/org/bdware/sc/units/ContractRecord.java new file mode 100644 index 0000000..d9621e6 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractRecord.java @@ -0,0 +1,84 @@ +package org.bdware.sc.units; + + +import java.io.*; + +/* + * 节点启动后记录自己的units信息和合约的一些信息 + * 节点重新上线后,NC发要求让其恢复,则从本地读取自己的这些信息进行恢复 + */ +public class ContractRecord implements Serializable { +/* private static final long serialVersionUID = 704775241674568688L; + + public transient RecoverFlag recoverFlag = RecoverFlag.Fine; + + public String contractID; + public String contractName; + public String key; + public String contractpubKey; + public ContractType type; + public int copies = 1; + public int lastExeSeq = -1; + public boolean isPrivate = false; + public String pubKeyPath; + public String ypkName; + + //public Map members; //k-udpid,v-udpaddress + + public String memory = ""; + + //JavaScriptEntry + public String invokeID; + + public ContractRecord(String id){ + this.contractID = id; + } + + public ContractRecord(String id,String contractName,String key,String contractpubKey,ContractType type,int copies){ + this.contractID = id; + this.contractName = contractName; + this.key = key; + this.contractpubKey = contractpubKey; + this.type = type; + this.copies = copies; + } + + public void printContent(){ + System.out.println("==========ContractRecord========"); + + System.out.println("contractID=" + contractID == null ? "null" : contractID); + System.out.println("contractName=" + contractName == null ? "null" : contractName); + System.out.println("key=" + key == null ? "null" : key); + System.out.println("contractPubKey=" + contractpubKey == null ? "null" : contractpubKey); + System.out.println("type=" + type == null ? "null" : type); + System.out.println("lastExeSeq=" + lastExeSeq == null ? "null" : lastExeSeq); + System.out.println("invokeID=" + invokeID == null ? "null" : invokeID); + System.out.println("copies=" + copies); + System.out.println("isPrivate=" + isPrivate); + System.out.println("pubKeyPath=" + pubKeyPath == null ? "null" : pubKeyPath); + System.out.println("ypkName=" + ypkName == null ? "null" : ypkName); + if(members != null){ + for(String k : members.keySet()){ + System.out.println("members " + k + "-" + members.get(k)); + } + } + System.out.println("memory=" + memory == null ? "null" : memory); + System.out.println("==========ContractRecord print finish======="); + } + + public static void getContentFromFile(String path){ + File file = new File(path); + try{ + FileInputStream os = new FileInputStream(file); + ObjectInputStream oos = new ObjectInputStream(os); + ContractRecord record = (ContractRecord) oos.readObject(); + record.printContent(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + }*/ +} diff --git a/src/main/java/org/bdware/sc/units/ContractUnitController.java b/src/main/java/org/bdware/sc/units/ContractUnitController.java new file mode 100644 index 0000000..d874cfd --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractUnitController.java @@ -0,0 +1,156 @@ +package org.bdware.sc.units; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.ContractManager; +import org.bdware.sc.conn.Node; +import org.bdware.sc.sequencing.SequencingAlgorithm; + +import java.io.Serializable; +import java.util.*; + +public class ContractUnitController { + private static final Logger LOGGER = LogManager.getLogger(ContractUnitController.class); + private final TrustfulExecutorConnection connection; + private final Map units; //这个节点上<合约id,合约集群>,其中每个ContractUnit中存储了对应合约id的其他节点udp信息 + ContractManager manager; + SequencingAlgorithmFactory algorithmFactory; + + public ContractUnitController( + TrustfulExecutorConnection connection, + ContractManager manager, + SequencingAlgorithmFactory factory) { + this.connection = connection; + this.manager = manager; + algorithmFactory = factory; + units = new HashMap<>(); + } + + public void onContractUnitMessage(final ContractUnitMessage cumsg, Node node) { + ContractUnit unit; + switch (cumsg.type) { + case Start: + ContractUnitStartRequest req = ContractUnitStartRequest.parse(cumsg.content); + unit = new ContractUnit(); + unit.connection = connection; + String result = manager.startContract(req.contract); + unit.contractID = req.contract.getID(); + unit.sequencingAlgorithm = algorithmFactory.create(req, unit); + unit.node2member = new HashMap<>(); + units.put(req.contract.getID(), unit); + + System.out.println( + "[ContractUnitController] startContract:" + + result + + " isMaster:" + + req.isMaster + + " cid:" + + req.contract.getID()); + break; + case AddMember: + LOGGER.debug("contractID:" + cumsg.getContractID()); + unit = units.get(cumsg.getContractID()); + unit.addMember(node, cumsg); //启动通过在UDPTrustExecutor中遍历memebers,使得这个合约集群中每个节点都有其他节点的UDP信息,便于之后护发UDP消息 + break; + case Sequencing: + unit = units.get(cumsg.getContractID()); + ContractUnitMember member = unit.node2member.get(node); + unit.sequencingAlgorithm.onMessage(member, cumsg.content); + break; + case Unknown: + default: + //recover start + ContractUnitStartRequest req2 = ContractUnitStartRequest.parse(cumsg.content); + unit = new ContractUnit(); + unit.connection = connection; + unit.contractID = req2.contract.getID(); + unit.sequencingAlgorithm = algorithmFactory.create(req2, unit); + unit.node2member = new HashMap<>(); + units.put(req2.contract.getID(), unit); + + System.out.println( + "[ContractUnitController] startContract:" + + " isMaster:" + + req2.isMaster + + " cid:" + + req2.contract.getID()); + break; + } + } + + public ContractUnit getContractUnit(String id) { + return units.get(id); + } + + public static class ContractUnit implements TrustfulExecutorConnection, Serializable { + public String contractID; + public Map node2member; //存储和自己在一个合约集群的其他节点信息 + private transient SequencingAlgorithm sequencingAlgorithm; + private transient TrustfulExecutorConnection connection; + + public synchronized void broadcast(ContractUnitMessage cuMessage) { + for (Node node : node2member.keySet()) { + connection.sendMessage(node, cuMessage.toByteArray()); + } + } + + public String addMember(Node node, ContractUnitMessage msg) { + ContractUnitMember member; + try { + // TODO + member = new ContractUnitMember(node); + sequencingAlgorithm.onMessage(member, msg.content); + node2member.put(node, member); + return "success"; + } catch (Exception e) { + e.printStackTrace(); + return "failed"; + } + } + + @Override + public void sendMessage(Node node, byte[] msg) { + ContractUnitMember member = (ContractUnitMember) node; + ContractUnitMessage unitMsg = new ContractUnitMessage(); + unitMsg.setContractID(contractID); + unitMsg.type = ContractUnitType.Sequencing; + unitMsg.content = msg; + System.out.println("[ContractUnitController] sendMsg:" + unitMsg.getDisplayStr()); + + // TODO sig here. + connection.sendMessage(member.getNode(), unitMsg.toByteArray()); + } + } + + static class PCInfo { + public ContractUnitMessage request; + Set prepare; + Set commit; + boolean isPrePrepareReceived; + boolean isSendCommit; + boolean isSendReply; + List buff; + + PCInfo() { + prepare = new HashSet<>(); + commit = new HashSet<>(); + buff = new ArrayList<>(); + isSendCommit = false; + isSendReply = false; + isPrePrepareReceived = false; + } + + public String getDisplayStr() { + return "pSize:" + + (prepare == null ? "null" : prepare.size()) + + " cSize:" + + (commit == null ? "null" : commit.size()) + + " isSendCommit:" + + isSendCommit + + " isSendReply:" + + isSendReply + + " buffSize:" + + buff.size(); + } + } +} diff --git a/src/main/java/org/bdware/sc/units/ContractUnitMember.java b/src/main/java/org/bdware/sc/units/ContractUnitMember.java new file mode 100644 index 0000000..6e449af --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractUnitMember.java @@ -0,0 +1,18 @@ +package org.bdware.sc.units; + +import org.bdware.sc.conn.Node; + +public class ContractUnitMember extends Node { + + private static final long serialVersionUID = 8175664649689078937L; + public Node node; + public String pubKey; + + public ContractUnitMember(Node node) { + this.node = node; + } + + public Node getNode() { + return node; + } +} \ No newline at end of file diff --git a/src/main/java/org/bdware/sc/units/ContractUnitMessage.java b/src/main/java/org/bdware/sc/units/ContractUnitMessage.java new file mode 100644 index 0000000..23da53d --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractUnitMessage.java @@ -0,0 +1,109 @@ +package org.bdware.sc.units; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.zz.gmhelper.BCECUtil; +import org.zz.gmhelper.SM2Util; + +public class ContractUnitMessage implements Serializable { + + private static final long serialVersionUID = 584934384202845750L; + ContractUnitType type; + private String contractID; + String requestID; + String signature; + byte[] content; + + public ContractUnitMessage(String requestID, String contractID, byte[] sender) { + this.requestID = requestID; + this.setContractID(contractID); + } + + public ContractUnitMessage() { + // TODO Auto-generated constructor stub + } + + public static ContractUnitMessage parse(byte[] b) { + try { + ByteArrayInputStream bi = new ByteArrayInputStream(b); + ObjectInputStream input = new ObjectInputStream(bi); + ContractUnitMessage ret = (ContractUnitMessage) input.readObject(); + return ret; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public byte[] toByteArray() { + try { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream output = new ObjectOutputStream(bo); + output.writeObject(this); + return bo.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + private static String IDA = "PBFTMsg"; + +// public void sign(NodeInfo info) { +// sender = info.getNodeID(); +// signature = sm02.sign(content, IDA, info.privKey).toString(); +// } + + public boolean verify(ContractUnitMember member) { + ECPublicKeyParameters param = BCECUtil.createECPublicKeyFromStrParameters(member.pubKey,SM2Util.CURVE,SM2Util.DOMAIN_PARAMS); + + return SM2Util.verify(param,content, signature.getBytes()); + } + + public void setType(ContractUnitType type) { + this.type = type; + } + + public ContractUnitType getType() { + return type; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public String getContractID() { + return contractID; + } + + public String getRequestID() { + return requestID; + } + + public String getDisplayStr() { + StringBuilder sb = new StringBuilder(); + sb.append(requestID); + sb.append("_TYPE_").append(type); + sb.append("_"); + sb.append(getContractID()); + sb.append("_"); + sb.append(" content:"); + sb.append(new String(content)); + return sb.toString(); + } + + public void setContractID(String contractID) { + this.contractID = contractID; + } + +} diff --git a/src/main/java/org/bdware/sc/units/ContractUnitStartRequest.java b/src/main/java/org/bdware/sc/units/ContractUnitStartRequest.java new file mode 100644 index 0000000..08582b5 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractUnitStartRequest.java @@ -0,0 +1,46 @@ +package org.bdware.sc.units; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.bdware.sc.bean.Contract; +import org.bdware.sc.conn.Node; + +public class ContractUnitStartRequest implements Serializable { + /** + * + */ + private static final long serialVersionUID = -1540780483790689627L; + public Contract contract; + public boolean isMaster; + public Node[] members; + + public static ContractUnitStartRequest parse(byte[] content) { + try { + ByteArrayInputStream input = new ByteArrayInputStream(content); + ObjectInputStream objInput; + objInput = new ObjectInputStream(input); + return (ContractUnitStartRequest) objInput.readObject(); + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public byte[] toByteArray() { + try { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream objOutput = new ObjectOutputStream(bo); + objOutput.writeObject(this); + return bo.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/org/bdware/sc/units/ContractUnitType.java b/src/main/java/org/bdware/sc/units/ContractUnitType.java new file mode 100644 index 0000000..019ad00 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ContractUnitType.java @@ -0,0 +1,30 @@ +package org.bdware.sc.units; + +import java.io.Serializable; + +public enum ContractUnitType implements Serializable { + + Start(0), AddMember(1), Sequencing(2), Unknown(3); + private int type; + + private ContractUnitType(int i) { + type = i; + } + + public static ContractUnitType fromByte(int i) { + switch (i) { + case 0: + return Start; + case 1: + return AddMember; + case 2: + return Sequencing; + default: + return Unknown; + } + } + + public int toInt() { + return type; + } +} diff --git a/src/main/java/org/bdware/sc/units/MultiContractMeta.java b/src/main/java/org/bdware/sc/units/MultiContractMeta.java new file mode 100644 index 0000000..2a619cb --- /dev/null +++ b/src/main/java/org/bdware/sc/units/MultiContractMeta.java @@ -0,0 +1,219 @@ +package org.bdware.sc.units; + +import com.google.gson.JsonArray; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.bean.ContractExecType; +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.bean.IDSerializable; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.KeyValueDBUtil; +import org.bdware.sc.redo.TransRecord; +import org.bdware.sc.util.JsonUtil; +import org.bdware.server.trustedmodel.ContractExecutor; +import org.bdware.server.trustedmodel.ContractUnitStatus; + +import java.util.Map; +import java.util.PriorityQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +// 在每个节点上均有 +public class MultiContractMeta implements IDSerializable { + private static final Logger LOGGER = LogManager.getLogger(MultiContractMeta.class); + private final AtomicInteger lastExeSeq; // last executed request seq + public volatile int curExeSeq = -1; // 当前正在执行请求序号 for multipoint contract requests + public String memory; + public String key; // privateKey + public String publicKey; + public String invokeID; // TODO + public ContractExecType type; + public ContractUnitStatus unitStatus = ContractUnitStatus.CommonMode; + public transient ContractExecutor contractExecutor; + public transient PriorityQueue queue; // contract request + public transient Map uniReqIDMap; // 用于请求 + public transient Map resultMap; // 用于请求 + public transient PriorityQueue trans_queue; // transRecord + AtomicInteger masterOrder = new AtomicInteger(-1); + private String contractID; + private boolean isPrivate = false; + private String pubKeyPath; + private String ypkName; + private String[] members; // 执行这个合约的所有节点的pubKey + private boolean isMaster; + private String masterNode; + + public MultiContractMeta(String s) { + contractID = s; + isMaster = false; + masterNode = null; + lastExeSeq = new AtomicInteger(-1); + initQueue(); + } + + public void initQueue() { + queue = new PriorityQueue<>(); + trans_queue = new PriorityQueue<>(); + uniReqIDMap = new ConcurrentHashMap<>(); + resultMap = new ConcurrentHashMap<>(); + } + + public int getCurSeqAtMaster() { + return masterOrder.get(); + } + + public void nextSeqAtMaster() { + masterOrder.getAndIncrement(); + } + + public void setSeqAtMaster(int seq) { + masterOrder = new AtomicInteger(seq); + } + + public void init(MultiContractMeta old) { + if (old != null && old.hasQueues()) { + queue = old.queue; + trans_queue = old.trans_queue; + uniReqIDMap = old.uniReqIDMap; + resultMap = old.resultMap; + } else { + if (null == queue) { + queue = new PriorityQueue<>(); + } + if (null == trans_queue) { + trans_queue = new PriorityQueue<>(); + } + if (null == uniReqIDMap) { + uniReqIDMap = new ConcurrentHashMap<>(); + } + if (null == resultMap) { + resultMap = new ConcurrentHashMap<>(); + } + } + } + + private boolean hasQueues() { + return null != queue && null != trans_queue && null != uniReqIDMap && null != resultMap; + } + + public int getLastExeSeq() { + return this.lastExeSeq.get(); + } + + public void setLastExeSeq(int lastExeSeq) { + this.lastExeSeq.set(lastExeSeq); + if (KeyValueDBUtil.instance.containsKey( + CMTables.LastExeSeq.toString(), contractID)) { // 如果现在是Stable模式就同步刷到磁盘 + KeyValueDBUtil.instance.setValue( + CMTables.LastExeSeq.toString(), contractID, String.valueOf(lastExeSeq)); + } + } + + public boolean isSequent(int a) { + return a - lastExeSeq.get() == 1; + } + + public String getContractID() { + return contractID; + } + + public void setContractID(String id) { + contractID = id; + } + + public String getID() { + return contractID; + } + + public void setIsPrivate(boolean t) { + isPrivate = t; + } + + public boolean isPrivate() { + return isPrivate; + } + + public String getPubKeyPath() { + if (!isPrivate) { + return null; + } + return pubKeyPath; + } + + public void setPubKeyPath(String p) { + pubKeyPath = p; + } + + public String getYpkName() { + return ypkName; + } + + public void setYpkName(String s) { + ypkName = s; + } + + public String[] getMembers() { + return members; + } + + public void setMembers(String[] m) { + members = m; + } + + public void setMembers(JsonArray members) { + String[] copied = new String[members.size()]; + for (int i = 0; i < members.size(); i++) { + copied[i] = members.get(i).getAsString(); + } + this.members = copied; + } + + public void printSet() { + for (ContractRequest cr : queue) { + LOGGER.debug("请求-" + cr.seq); + } + } + + public void addRequestQueue(ContractRequest request, String uniReqID, ResultCallback result) { + LOGGER.info("put to queue, seq:" + request.seq + " queue.size:" + queue.size()); + queue.add(request); + int seq = request.seq; + uniReqIDMap.put(seq, uniReqID); + resultMap.put(seq, result); + } + + public void printContent() { + System.out.println("contractID=" + (contractID == null ? "null" : contractID)); + System.out.println("isPrivate=" + isPrivate); + System.out.println("pubKeyPath=" + (pubKeyPath == null ? "null" : pubKeyPath)); + System.out.println("ypkName=" + (ypkName == null ? "null" : ypkName)); + System.out.println("key=" + (key == null ? "null" : key)); + System.out.println("publicKey=" + (publicKey == null ? "null" : publicKey)); + System.out.println("invokeID=" + (invokeID == null ? "null" : invokeID)); + System.out.println("type=" + (type == null ? "null" : type)); + System.out.println("lastExeSeq=" + lastExeSeq); + System.out.println("members=" + (members == null ? "null" : JsonUtil.toJson(members))); + System.out.println("memory=" + (memory == null ? "null" : memory)); + } + + public void setIsMaster(boolean isMaster) { + this.isMaster = isMaster; + } + + public boolean isMaster() { + return isMaster; + } + + public void setMaster(String master) { + masterNode = master; + } + + public String getMasterNode() { + return masterNode; + } + + public void setContractExecutor(ContractExecutor contractExecutor) { + this.contractExecutor = contractExecutor; + } +} diff --git a/src/main/java/org/bdware/sc/units/RecoverFlag.java b/src/main/java/org/bdware/sc/units/RecoverFlag.java new file mode 100644 index 0000000..53ae11b --- /dev/null +++ b/src/main/java/org/bdware/sc/units/RecoverFlag.java @@ -0,0 +1,5 @@ +package org.bdware.sc.units; + +public enum RecoverFlag { + Fine,ToRecover,Recovering; +} diff --git a/src/main/java/org/bdware/sc/units/RespCache.java b/src/main/java/org/bdware/sc/units/RespCache.java new file mode 100644 index 0000000..5c5acad --- /dev/null +++ b/src/main/java/org/bdware/sc/units/RespCache.java @@ -0,0 +1,53 @@ +package org.bdware.sc.units; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.atomic.AtomicInteger; + +public class RespCache { + private static final Logger LOGGER = LogManager.getLogger(RespCache.class); + public String val = null; + public int count; // copies + public long time; + public boolean isExecuting = false; + AtomicInteger waiter = new AtomicInteger(0); + boolean timeout = true; + + public boolean waitForHalf() { + try { + synchronized (waiter) { + LOGGER.info("waitForHalf, " + waiter.get() + " count:" + count); + time = System.currentTimeMillis(); + if (waiter.incrementAndGet() * 2 > count) { + return timeout; + } else { + waiter.wait(5000L); + timeout &= waiter.get() * 2 > count; + if (!timeout) waiter.notifyAll(); + return timeout; + } + } + } catch (Exception e) { + e.printStackTrace(); + timeout &= waiter.get() * 2 > count; + return timeout; + } + } + + public void notifyAllWaiter() { + synchronized (waiter) { + waiter.notifyAll(); + } + } + + public void waitForResult() { + synchronized (waiter) { + try { + waiter.wait(5000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/org/bdware/sc/units/ResultCache.java b/src/main/java/org/bdware/sc/units/ResultCache.java new file mode 100644 index 0000000..089f7e3 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/ResultCache.java @@ -0,0 +1,7 @@ +package org.bdware.sc.units; + +public class ResultCache { + public String val = null; + public long time; + boolean timeout = true; +} diff --git a/src/main/java/org/bdware/sc/units/SequencingAlgorithmFactory.java b/src/main/java/org/bdware/sc/units/SequencingAlgorithmFactory.java new file mode 100644 index 0000000..9afb41a --- /dev/null +++ b/src/main/java/org/bdware/sc/units/SequencingAlgorithmFactory.java @@ -0,0 +1,8 @@ +package org.bdware.sc.units; + +import org.bdware.sc.sequencing.SequencingAlgorithm; +import org.bdware.sc.units.ContractUnitController.ContractUnit; + +public interface SequencingAlgorithmFactory { + public SequencingAlgorithm create(ContractUnitStartRequest req, ContractUnit unit); +} diff --git a/src/main/java/org/bdware/sc/units/TrustfulExecutorConnection.java b/src/main/java/org/bdware/sc/units/TrustfulExecutorConnection.java new file mode 100644 index 0000000..c105944 --- /dev/null +++ b/src/main/java/org/bdware/sc/units/TrustfulExecutorConnection.java @@ -0,0 +1,7 @@ +package org.bdware.sc.units; + +import org.bdware.sc.conn.Node; + +public interface TrustfulExecutorConnection { + public void sendMessage(Node node, byte[] msg); +} \ No newline at end of file diff --git a/src/main/java/org/bdware/server/trustedmodel/ContractExecutor.java b/src/main/java/org/bdware/server/trustedmodel/ContractExecutor.java new file mode 100644 index 0000000..08efce3 --- /dev/null +++ b/src/main/java/org/bdware/server/trustedmodel/ContractExecutor.java @@ -0,0 +1,8 @@ +package org.bdware.server.trustedmodel; + +import org.bdware.sc.bean.ContractRequest; +import org.bdware.sc.conn.ResultCallback; + +public interface ContractExecutor { + void execute(String requestID, ResultCallback rc, ContractRequest req); +} diff --git a/src/main/java/org/bdware/server/trustedmodel/ContractUnitStatus.java b/src/main/java/org/bdware/server/trustedmodel/ContractUnitStatus.java new file mode 100644 index 0000000..26d5aa2 --- /dev/null +++ b/src/main/java/org/bdware/server/trustedmodel/ContractUnitStatus.java @@ -0,0 +1,8 @@ +package org.bdware.server.trustedmodel; + +import java.io.Serializable; + +public enum ContractUnitStatus implements Serializable { + CommonMode, + StableMode; +} diff --git a/src/test/java/org/bdware/sc/test/ContractManagerTest.java b/src/test/java/org/bdware/sc/test/ContractManagerTest.java new file mode 100644 index 0000000..d0dd955 --- /dev/null +++ b/src/test/java/org/bdware/sc/test/ContractManagerTest.java @@ -0,0 +1,10 @@ +package org.bdware.sc.test; + +import org.bdware.sc.ContractManager; + +public class ContractManagerTest { + public static void main(String[] args) { + ContractManager.yjsPath = "./generatedlib/yjs.jar"; + ContractManager.instance = new ContractManager(); + } +} diff --git a/src/test/java/org/bdware/sc/units/RespCacheTest.java b/src/test/java/org/bdware/sc/units/RespCacheTest.java new file mode 100644 index 0000000..4e2efc1 --- /dev/null +++ b/src/test/java/org/bdware/sc/units/RespCacheTest.java @@ -0,0 +1,78 @@ +package org.bdware.sc.units; + +import org.junit.Test; + +public class RespCacheTest { + @Test + public void timeOutAllFalse() throws InterruptedException { + RespCache cache = new RespCache(); + cache.count = 7; + for (int i = 0; i < 7; i++) { + final int j = i; + new Thread(() -> { + try { + Thread.sleep(j * 1000); + if (j > 2) Thread.sleep(6 * 1000); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + boolean waitResult = cache.waitForHalf(); + System.out.println( + "tid:" + + Thread.currentThread().getId() + + " reach target, waitResult:" + + waitResult); + }).start(); + } + Thread.sleep(20000); + } + + @Test + public void inTimeAllTrue() throws InterruptedException { + RespCache cache = new RespCache(); + cache.count = 7; + for (int i = 0; i < 7; i++) { + final int j = i; + new Thread(() -> { + try { + Thread.sleep(j * 1000); + if (j > 2) Thread.sleep(1900); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + boolean waitResult = cache.waitForHalf(); + System.out.println( + "tid:" + + Thread.currentThread().getId() + + " reach target, waitResult:" + + waitResult); + }).start(); + } + Thread.sleep(20000); + } + + @Test + public void fastAllTrue() throws InterruptedException { + RespCache cache = new RespCache(); + cache.count = 7; + for (int i = 0; i < 7; i++) { + new Thread(() -> { + try { + Thread.sleep(1000); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + boolean waitResult = cache.waitForHalf(); + System.out.println( + "tid:" + + Thread.currentThread().getId() + + " reach target, waitResult:" + + waitResult); + }).start(); + } + Thread.sleep(10000); + } +}