mirror of
https://gitee.com/BDWare/cm
synced 2025-01-09 17:34:04 +00:00
initial commit
This commit is contained in:
parent
35eed0d99a
commit
75cc8d39bc
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/build/
|
||||||
|
*/build/*
|
||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
34
build.gradle
Normal file
34
build.gradle
Normal file
@ -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'
|
||||||
|
}
|
30
src/main/java/org/bdware/sc/ChainOpener.java
Normal file
30
src/main/java/org/bdware/sc/ChainOpener.java
Normal file
@ -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);
|
||||||
|
}
|
501
src/main/java/org/bdware/sc/ContractClient.java
Normal file
501
src/main/java/org/bdware/sc/ContractClient.java
Normal file
@ -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<Map<String, REventSemantics>>() {
|
||||||
|
}.getType());
|
||||||
|
// LOGGER.info("initProps ---- position-----3");
|
||||||
|
contractMeta.exportedFunctions =
|
||||||
|
JsonUtil.fromJson(
|
||||||
|
get.syncGet("", "getExportedFunctions", ""),
|
||||||
|
new TypeToken<List<FunctionDesp>>() {
|
||||||
|
}.getType());
|
||||||
|
contractMeta.dependentContracts = JsonUtil.fromJson(
|
||||||
|
get.syncGet("", "getDependentContracts", ""),
|
||||||
|
new TypeToken<Set<String>>() {
|
||||||
|
}.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<List<AnnotationNode>>() {
|
||||||
|
}.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<String, String> 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<String, REventSemantics> getEvents() {
|
||||||
|
return contractMeta.declaredEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FunctionDesp> 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<AnnotationNode> 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;
|
||||||
|
}
|
||||||
|
}
|
2328
src/main/java/org/bdware/sc/ContractManager.java
Normal file
2328
src/main/java/org/bdware/sc/ContractManager.java
Normal file
File diff suppressed because it is too large
Load Diff
119
src/main/java/org/bdware/sc/ContractMeta.java
Normal file
119
src/main/java/org/bdware/sc/ContractMeta.java
Normal file
@ -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<String, REvent.REventSemantics> declaredEvents;
|
||||||
|
List<FunctionDesp> exportedFunctions;
|
||||||
|
Map<String, String> logDetail;
|
||||||
|
List<AnnotationNode> annotations;
|
||||||
|
Set<String> dependentContracts;
|
||||||
|
transient Map<String, FunctionDesp> funCache;
|
||||||
|
boolean sigRequired;
|
||||||
|
String thisPermission; // 合约当前权限
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"name": "dx_substr",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"columnIndex":5,
|
||||||
|
"paras":["1","3"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
// Map<Object,Object>MaskInfo;
|
||||||
|
|
||||||
|
public ContractMeta() {}
|
||||||
|
|
||||||
|
public ContractStatusEnum getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getID() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AnnotationNode> getAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FunctionDesp> getExportedFunctions() {
|
||||||
|
return exportedFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getDependentContracts() { return dependentContracts; }
|
||||||
|
|
||||||
|
public Map<String, REvent.REventSemantics> 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;
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/org/bdware/sc/ContractStatusEnum.java
Normal file
43
src/main/java/org/bdware/sc/ContractStatusEnum.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
423
src/main/java/org/bdware/sc/ContractStatusRecorder.java
Normal file
423
src/main/java/org/bdware/sc/ContractStatusRecorder.java
Normal file
@ -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<ContractMeta> {
|
||||||
|
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<String, ContractClient> id2ContractClient;
|
||||||
|
|
||||||
|
public LRUList<ContractMeta> 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<String, ContractStatusEnum> 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<String> 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<String> 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<ContractMeta> 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<Integer> existed = new HashSet<>(); // 已经连上的合约端口
|
||||||
|
private final Set<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/org/bdware/sc/MasterElectTimeRecorder.java
Normal file
15
src/main/java/org/bdware/sc/MasterElectTimeRecorder.java
Normal file
@ -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恢复结束
|
||||||
|
}
|
15
src/main/java/org/bdware/sc/MasterStub.java
Normal file
15
src/main/java/org/bdware/sc/MasterStub.java
Normal file
@ -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);
|
||||||
|
}
|
47
src/main/java/org/bdware/sc/MultiContractRecorder.java
Normal file
47
src/main/java/org/bdware/sc/MultiContractRecorder.java
Normal file
@ -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<MultiContractMeta> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
46
src/main/java/org/bdware/sc/ProjectRecorder.java
Normal file
46
src/main/java/org/bdware/sc/ProjectRecorder.java
Normal file
@ -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<ProjectConfig> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java
Normal file
44
src/main/java/org/bdware/sc/RecoverMechTimeRecorder.java
Normal file
@ -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<String,Long> masterStartRecoverNode = new HashMap<String, Long>(); //master的askForRecover被调用
|
||||||
|
|
||||||
|
public static Map<String,Long> startJudgeRecoverMethod = new HashMap<String,Long>(); //开始判断某个节点的恢复方式
|
||||||
|
public static Map<String,Long> judgeRecoverMethodFinish = new HashMap<String,Long>(); //判断某节点恢复方式完成
|
||||||
|
|
||||||
|
public static Map<String,Long> startRecoverFromCommon = new HashMap<String,Long>(); //slave从common恢复master的nodeRestartFromCommonMode方法开始时间
|
||||||
|
public static Map<String,Long> writeCEIStart = new HashMap<String, Long>(); //开始dumpContract等cei信息
|
||||||
|
public static Map<String,Long> finishWriteCEI = new HashMap<String, Long>(); //将cei写入文件完成
|
||||||
|
|
||||||
|
public static Map<String,Long> startRecoverFromStable = new HashMap<String,Long>(); //slave从common恢复master的restartFromStableMode方法开始时间
|
||||||
|
|
||||||
|
public static Map<String,Long> recoverFinish = new HashMap<String,Long>();
|
||||||
|
}
|
479
src/main/java/org/bdware/sc/event/EventBroker.java
Normal file
479
src/main/java/org/bdware/sc/event/EventBroker.java
Normal file
@ -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<String, Set<String>> topic2cIds;
|
||||||
|
private final Map<String, IEventConsumer> id2Consumers;
|
||||||
|
private final Map<String, ThreadFlag> threadFlags;
|
||||||
|
private final Map<String, Long> tempTopics;
|
||||||
|
// private final Map<String, Stack<REvent>> 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<String, Set<String>> topic2cIds,
|
||||||
|
Map<String, IEventConsumer> 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<br/>
|
||||||
|
* topic and consumer must not be null at the same time
|
||||||
|
* <ul>
|
||||||
|
* <li>if consumer is null and topic is not, it means the topic is a temporary topic, remove it</li>
|
||||||
|
* <li>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</li>
|
||||||
|
* <li>if two of them is not null, do unsubscribing in two registries</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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<String, Set<String>> topic2cIds,
|
||||||
|
Map<String, IEventConsumer> 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<String> 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<String> topic2Id = topic2cIds.get(topic);
|
||||||
|
toRmIds.forEach(topic2Id::remove);
|
||||||
|
LOGGER.info("contract " + contract + " unsubscribes topic " + topic);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse consumer information from content str<br/>
|
||||||
|
* 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/main/java/org/bdware/sc/event/EventCenter.java
Normal file
161
src/main/java/org/bdware/sc/event/EventCenter.java
Normal file
@ -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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
237
src/main/java/org/bdware/sc/event/EventRecorder.java
Normal file
237
src/main/java/org/bdware/sc/event/EventRecorder.java
Normal file
@ -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<String, Set<String>> topic2cIds,
|
||||||
|
Map<String, IEventConsumer> 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<Object> 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<String, Long> tempTopics) {
|
||||||
|
KeyValueRocksDBUtil.instance.setValue(dbName, TEMP_TOPIC_KEYS, JsonUtil.toJson(tempTopics));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Long> recoverTempTopicsFromDb() {
|
||||||
|
String json = KeyValueRocksDBUtil.instance.getValue(dbName, TEMP_TOPIC_KEYS);
|
||||||
|
Map<String, Long> 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<String, Set<String>> topic2cIds;
|
||||||
|
Map<String, IEventConsumer> id2Consumers;
|
||||||
|
String prev;
|
||||||
|
|
||||||
|
public CheckPoint() {
|
||||||
|
topic2cIds = new ConcurrentHashMap<>();
|
||||||
|
id2Consumers = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CheckPoint(Map<String, Set<String>> topic2cIds,
|
||||||
|
Map<String, IEventConsumer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
119
src/main/java/org/bdware/sc/event/clients/ContractConsumer.java
Normal file
119
src/main/java/org/bdware/sc/event/clients/ContractConsumer.java
Normal file
@ -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<String, ScheduledFuture<?>> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
39
src/main/java/org/bdware/sc/event/clients/NodeConsumer.java
Normal file
39
src/main/java/org/bdware/sc/event/clients/NodeConsumer.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
101
src/main/java/org/bdware/sc/handler/ManagerHandler.java
Normal file
101
src/main/java/org/bdware/sc/handler/ManagerHandler.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
7
src/main/java/org/bdware/sc/sequencing/Committer.java
Normal file
7
src/main/java/org/bdware/sc/sequencing/Committer.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package org.bdware.sc.sequencing;
|
||||||
|
|
||||||
|
import org.bdware.sc.bean.ContractRequest;
|
||||||
|
|
||||||
|
public interface Committer {
|
||||||
|
void onCommit(ContractRequest data);
|
||||||
|
}
|
382
src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java
Normal file
382
src/main/java/org/bdware/sc/sequencing/PBFTAlgorithm.java
Normal file
@ -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<Node, PBFTMember> members;
|
||||||
|
AtomicLong allocatedID = new AtomicLong(0);
|
||||||
|
Map<Long, PCInfo> info;
|
||||||
|
List<PBFTMessage> commitedMsg;
|
||||||
|
AtomicLong commitedOrder = new AtomicLong(0);
|
||||||
|
Map<Integer, Pair<Node, PBFTMessage>> 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<Node, PBFTMessage>(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<PBFTMessage> 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<Integer> prepare;
|
||||||
|
Set<Integer> commit;
|
||||||
|
boolean isPrePrepareReceived;
|
||||||
|
boolean isSendCommit;
|
||||||
|
boolean isSendReply;
|
||||||
|
List<PBFTMessage> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/org/bdware/sc/sequencing/PBFTMember.java
Normal file
43
src/main/java/org/bdware/sc/sequencing/PBFTMember.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
99
src/main/java/org/bdware/sc/sequencing/PBFTMessage.java
Normal file
99
src/main/java/org/bdware/sc/sequencing/PBFTMessage.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/org/bdware/sc/sequencing/PBFTType.java
Normal file
35
src/main/java/org/bdware/sc/sequencing/PBFTType.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/org/bdware/sc/sequencing/Pair.java
Normal file
11
src/main/java/org/bdware/sc/sequencing/Pair.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package org.bdware.sc.sequencing;
|
||||||
|
|
||||||
|
public class Pair<T1, T2> {
|
||||||
|
public T1 first;
|
||||||
|
public T2 second;
|
||||||
|
|
||||||
|
public Pair(T1 t1, T2 t2) {
|
||||||
|
first = t1;
|
||||||
|
second = t2;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
31
src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java
Normal file
31
src/main/java/org/bdware/sc/sequencing/ViewAlgorithm.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/main/java/org/bdware/sc/units/ByteUtil.java
Normal file
53
src/main/java/org/bdware/sc/units/ByteUtil.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
84
src/main/java/org/bdware/sc/units/ContractRecord.java
Normal file
84
src/main/java/org/bdware/sc/units/ContractRecord.java
Normal file
@ -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<String,String> 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();
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
156
src/main/java/org/bdware/sc/units/ContractUnitController.java
Normal file
156
src/main/java/org/bdware/sc/units/ContractUnitController.java
Normal file
@ -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<String, ContractUnit> 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<Node, ContractUnitMember> 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<Integer> prepare;
|
||||||
|
Set<Integer> commit;
|
||||||
|
boolean isPrePrepareReceived;
|
||||||
|
boolean isSendCommit;
|
||||||
|
boolean isSendReply;
|
||||||
|
List<ContractUnitMessage> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/main/java/org/bdware/sc/units/ContractUnitMember.java
Normal file
18
src/main/java/org/bdware/sc/units/ContractUnitMember.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
109
src/main/java/org/bdware/sc/units/ContractUnitMessage.java
Normal file
109
src/main/java/org/bdware/sc/units/ContractUnitMessage.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/main/java/org/bdware/sc/units/ContractUnitType.java
Normal file
30
src/main/java/org/bdware/sc/units/ContractUnitType.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
219
src/main/java/org/bdware/sc/units/MultiContractMeta.java
Normal file
219
src/main/java/org/bdware/sc/units/MultiContractMeta.java
Normal file
@ -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<ContractRequest> queue; // contract request
|
||||||
|
public transient Map<Integer, String> uniReqIDMap; // 用于请求
|
||||||
|
public transient Map<Integer, ResultCallback> resultMap; // 用于请求
|
||||||
|
public transient PriorityQueue<TransRecord> 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;
|
||||||
|
}
|
||||||
|
}
|
5
src/main/java/org/bdware/sc/units/RecoverFlag.java
Normal file
5
src/main/java/org/bdware/sc/units/RecoverFlag.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package org.bdware.sc.units;
|
||||||
|
|
||||||
|
public enum RecoverFlag {
|
||||||
|
Fine,ToRecover,Recovering;
|
||||||
|
}
|
53
src/main/java/org/bdware/sc/units/RespCache.java
Normal file
53
src/main/java/org/bdware/sc/units/RespCache.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/main/java/org/bdware/sc/units/ResultCache.java
Normal file
7
src/main/java/org/bdware/sc/units/ResultCache.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package org.bdware.sc.units;
|
||||||
|
|
||||||
|
public class ResultCache {
|
||||||
|
public String val = null;
|
||||||
|
public long time;
|
||||||
|
boolean timeout = true;
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.bdware.server.trustedmodel;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public enum ContractUnitStatus implements Serializable {
|
||||||
|
CommonMode,
|
||||||
|
StableMode;
|
||||||
|
}
|
10
src/test/java/org/bdware/sc/test/ContractManagerTest.java
Normal file
10
src/test/java/org/bdware/sc/test/ContractManagerTest.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
78
src/test/java/org/bdware/sc/units/RespCacheTest.java
Normal file
78
src/test/java/org/bdware/sc/units/RespCacheTest.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user