2021SC@SDUSC
Table of contents
1. The interface of the contract model
1.ContractAware
The sub-interfaces implemented by the contract can listen to lifecycle events at runtime.
public interface ContractAware {
}
2.ContractEventContext
The interface imports the HashDigest interface, and this method is used to obtain the current ledger hash.
HashDigest getCurrentLedgerHash();
Execution of transaction requests for contract events
TransactionRequest getTransactionRequest();
The code returns the set of signers for the transaction
Set<BlockchainIdentity> getTxSigners();
The code returns the event name
String getEvent();
The code returns the parameter list
BytesValueList getArgs();
Ledger operation context
LedgerContext getLedger();
The collection of contract owners, the contract owner is the collection of signers when signing the contract
Set<BlockchainIdentity> getContractOwners();
We know that each contract or ledger must have a method to obtain the latest version, and this method is used to obtain the latest version of the contract.
long getVersion();
Currently contains the uncommitted block data ledger operation context.
LedgerQueryService getUncommittedLedger();
3.ContractLifeCycleAware
The contract implements this interface to monitor the life cycle events of the contract application
public interface ContractLifecycleAware extends ContractAware {
void postConstruct();
void beforeDestroy();
}
4.ContractProcessor
This is an interface of a contract processor, mainly used for contract legality verification, contract analysis, and providing contract entry and decompilation
Contract validity verification, the first one is to enter a user name to verify, the method will give you a feedback message, the second is to enter the block password, and you will also get a feedback.
boolean verify(File carFile) throws Exception;
boolean verify(byte[] chainCode) throws Exception;
These two methods are the contract entry, and you can enter the contract by entering the user name and password respectively.
ContractEntrance analyse(File carFile) throws Exception;
ContractEntrance analyse(byte[] chainCode) throws Exception;
These two are decompiled entries
String decompileEntranceClass(File carFile) throws Exception;
String decompileEntranceClass(byte[] chainCode) throws Exception;
2. The class of the contract model
1.ContractType
Here is its import
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.jd.blockchain.ledger.BytesValueEncoding;
import utils.IllegalDataException;
We can see that the class has created three private variables, namely the contract name and two hash maps of the contract
private String name;
private Map<String, Method> events = new HashMap<>();
private Map<Method, String> handleMethods = new HashMap<>();
This method is used to get the name of the contract.
public String getName() {
return name;
}
method returns all events declared
public Set<String> getEvents() {
return events.keySet();
}
The code returns the event declared by the specified method, or null if it does not exist
public String getEvent(Method method) {
return handleMethods.get(method);
}
The code returns the event handling method, if not, returns null
public Method getHandleMethod(String event) {
return events.get(event);
}
Parse the declaration of the contract, enter the content code of the contract to execute its parsing work, the method first checks the type of the contract method declaration and the return value type, if it is a class, it will first obtain its interface, because the code is too long, I will other parts The analysis is written in the comments
public static ContractType resolve(Class<?> contractIntf) {
if (!contractIntf.isInterface()) {
Class<?> realIntf = null;
Class<?>[] interfaces = contractIntf.getInterfaces();
for (Class<?> intf : interfaces) {
if (intf.isAnnotationPresent(Contract.class)) {
realIntf = intf;
break;
}
}
if (realIntf == null) {
throw new IllegalDataException(String
.format("%s is not a Contract Type, because there is not @Contract !", contractIntf.getName()));
}
contractIntf = realIntf;
}
// 接口上必须有注解
Contract contract = contractIntf.getAnnotation(Contract.class);
if (contract == null) {
throw new IllegalDataException("It is not a Contract Type, because there is not @Contract !");
}
Method[] classMethods = contractIntf.getDeclaredMethods();
if (classMethods.length == 0) {
throw new IllegalDataException("This interface have not any methods !");
}
ContractType contractType = new ContractType();
// 设置合约显示名字为
contractType.name = contract.name();
for (Method method : classMethods) {
// if current method contains @ContractEvent,then put it in this map;
ContractEvent contractEvent = method.getAnnotation(ContractEvent.class);
if (contractEvent != null) {
String eventName = contractEvent.name();
// if annoMethodMap has contained the eventName, too many same eventNames exists
// probably, say NO!
if (contractType.events.containsKey(eventName)) {
throw new ContractException("there is repeat definition of contractEvent to @ContractEvent.");
}
// check param's type is fit for need.
Class<?>[] paramTypes = method.getParameterTypes();
for (Class<?> currParamType : paramTypes) {
if (!BytesValueEncoding.supportType(currParamType)) {
throw new IllegalStateException(
String.format("Param Type = %s can not support !!!", currParamType.getName()));
}
}
// 判断返回值是否可序列化
Class<?> returnType = method.getReturnType();
if (!BytesValueEncoding.supportType(returnType)) {
throw new IllegalStateException(
String.format("Return Type = %s can not support !!!", returnType.getName()));
}
contractType.events.put(eventName, method);
contractType.handleMethods.put(method, eventName);
}
}
// 最起码有一个ContractEvent
if (contractType.events.isEmpty()) {
throw new IllegalStateException(
String.format("Contract Interface[%s] have none method for annotation[@ContractEvent] !", contractIntf.getName()));
}
return contractType;
}
This code will return the contract type
public String toString() {
return "ContractType{" + "name='" + name + '\'' + ", events=" + events + ", handleMethods=" + handleMethods
+ '}';
}
2.ContractJarUtils
This class defines these variables and fixed values at the beginning, which we will use in the next method.
public static final String BLACK_CONF = "filter.black.conf";
private static final String CONTRACT_MF = "META-INF/CONTRACT.MF";
private static final Random FILE_RANDOM = new Random();
private static final byte[] JDCHAIN_MARK = "JDChain".getBytes(StandardCharsets.UTF_8);
public static final String JDCHAIN_PACKAGE = "com.jd.blockchain";
This method is used to judge whether a package is a jdchain package, input the name of the package, the code is judged by the conditional statement if, if it is, return true, if not, return JDCHAIN_PACKAGE.
public static boolean isJDChainPackage(String packageName) {
if (packageName.equals(JDCHAIN_PACKAGE)) {
return true;
}
return packageName.startsWith(JDCHAIN_PACKAGE + ".");
}
This method solves the configuration problem of the contract. Enter the name of the file, the method will create a new array list, import the corresponding file into the method, and judge whether it is empty. If it is not empty, each sentence of the file will be looped, and then add The "," delimiter will eventually be loaded into a list created at the beginning of the code. If the file is empty, an error message will be returned.
public static List<String> resolveConfig(String fileName) {
List<String> configs = new ArrayList<>();
try {
List<String> readLines = loadConfig(fileName);
if (!readLines.isEmpty()) {
for (String readLine : readLines) {
String[] lines = readLine.split(",");
configs.addAll(Arrays.asList(lines));
}
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
return configs;
}
This method is used to load the configuration of the contract, which is also the name of the input file, and the code will call the method in I OUTils to print out the file information
public static List<String> loadConfig(String fileName) throws Exception {
return IOUtils.readLines(
ContractJarUtils.class.getResourceAsStream(File.separator + fileName));
}
This code is used to load all classes, input the jare package of file, you can enter the loop of the method, get the element, get the name, and after verifying the name, you can continuously print out the class information.
public static Map<String, byte[]> loadAllClasses(final File jar) throws Exception {
Map<String, byte[]> allClasses = new HashMap<>();
JarFile jarFile = new JarFile(jar);
Enumeration<JarEntry> jarEntries = jarFile.entries();
while(jarEntries.hasMoreElements()){
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();
if (verify(entryName)) {
byte[] classContent = readStream(jarFile.getInputStream(jarEntry));
if (classContent != null && classContent.length > 0) {
allClasses.put(entryName, classContent);
}
}
}
jarFile.close();
return allClasses;
}
This method is used to verify the entry name
private static boolean verify(String entryName) {
if (entryName.endsWith(".class")
&& !entryName.startsWith("META-INF")
&& !entryName.contains("-")
&& entryName.contains("/")) {
return true;
}
return false;
}
This method is used to add a dot (.) before the class
public static String dotClassName(String className) {
String dotClassName = className;
if (className.endsWith(".class")) {
dotClassName = className.substring(0, className.length() - 6);
}
dotClassName = dotClassName.replaceAll("/", ".");
return dotClassName;
}
This method inputs the various jars of the file, etc., to copy the information in the contract.
public static void copy(File srcJar, File dstJar, JarEntry addEntry, byte[] addBytes, String filter) throws IOException {
JarFile jarFile = new JarFile(srcJar);
Enumeration<JarEntry> jarEntries = jarFile.entries();
JarOutputStream jarOut = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(dstJar)));
while(jarEntries.hasMoreElements()){
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();
if (filter != null && filter.equals(entryName)) {
continue;
}
jarOut.putNextEntry(jarEntry);
jarOut.write(readStream(jarFile.getInputStream(jarEntry)));
jarOut.closeEntry();
}
if (addEntry != null) {
jarOut.putNextEntry(addEntry);
jarOut.write(addBytes);
jarOut.closeEntry();
}
jarOut.flush();
jarOut.finish();
jarOut.close();
jarFile.close();
}