[Software Engineering and Practice] (8) Data Account Model Framework, Contract Framework Model

2021SC@SDUSC

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();
    }

Guess you like

Origin blog.csdn.net/weixin_45932150/article/details/121451901