diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java deleted file mode 100644 index 8ed9b38e..00000000 --- a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/CheckImportsMojo.java +++ /dev/null @@ -1,128 +0,0 @@ -//package com.jd.blockchain; -// -//import com.github.javaparser.JavaParser; -//import com.github.javaparser.ast.CompilationUnit; -//import com.github.javaparser.ast.ImportDeclaration; -//import com.github.javaparser.ast.NodeList; -//import com.github.javaparser.ast.PackageDeclaration; -//import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -//import com.github.javaparser.ast.body.MethodDeclaration; -//import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -//import com.jd.blockchain.contract.ContractType; -//import com.jd.blockchain.utils.IllegalDataException; -//import org.apache.maven.plugin.AbstractMojo; -//import org.apache.maven.plugin.MojoFailureException; -//import org.apache.maven.plugins.annotations.Mojo; -//import org.apache.maven.plugins.annotations.Parameter; -//import org.apache.maven.project.MavenProject; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import java.io.File; -//import java.io.IOException; -//import java.io.InputStream; -//import java.net.URL; -//import java.net.URLClassLoader; -//import java.nio.file.Files; -//import java.nio.file.Path; -//import java.util.List; -//import java.util.Properties; -//import java.util.jar.Attributes; -//import java.util.jar.JarFile; -//import java.util.stream.Collectors; -// -///** -// * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. -// * This is a try of "from Initail to Abandoned". -// * Since we are good at the class, why not? -// * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. -// * -// * by zhaogw -// * date 2019-06-05 16:17 -// */ -//@Mojo(name = "checkImports") -//public class CheckImportsMojo extends AbstractMojo { -// -// Logger logger = LoggerFactory.getLogger(CheckImportsMojo.class); -// -// @Parameter(defaultValue = "${project}", required = true, readonly = true) -// private MavenProject project; -// -// /** -// * jar's name; -// */ -// @Parameter -// private String finalName; -// -// @Override -// public void execute() throws MojoFailureException { -// List sources; -// try { -// InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); -// Properties properties = new Properties(); -// properties.load(inputStream); -// String[] packageBlackList = properties.getProperty("blacklist").split(","); -// Path baseDirPath = project.getBasedir().toPath(); -// sources = Files.find(baseDirPath, Integer.MAX_VALUE, (file, attrs) -> (file.toString().endsWith(".java"))).collect(Collectors.toList()); -// for (Path path : sources) { -// CompilationUnit compilationUnit = JavaParser.parse(path); -// -// compilationUnit.accept(new MethodVisitor(), null); -// -// NodeList imports = compilationUnit.getImports(); -// for (ImportDeclaration imp : imports) { -// String importName = imp.getName().asString(); -// for (String item : packageBlackList) { -// if (importName.startsWith(item)) { -// throw new MojoFailureException("在源码中不允许包含此引入包:" + importName); -// } -// } -// } -// -// //now we parse the jar; -// String jarPath = project.getBuild().getDirectory()+ File.separator+finalName+".jar"; -// File jarFile = new File(jarPath); -// URL jarURL = jarFile.toURI().toURL(); -// ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL},this.getClass().getClassLoader()); -// Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); -// String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); -// try { -// Class mainClass = classLoader.loadClass(contractMainClass); -// ContractType.resolve(mainClass); -// } catch (ClassNotFoundException e) { -// throw new IllegalDataException(e.getMessage()); -// } -// } -// } catch (IOException exception) { -// logger.error(exception.getMessage()); -// throw new MojoFailureException("IO ERROR"); -// } catch (NullPointerException e) { -// logger.error(e.getMessage()); -// } -// } -// -// private class MethodVisitor extends VoidVisitorAdapter { -// @Override -// public void visit(MethodDeclaration n, Void arg) { -// /* here you can access the attributes of the method. -// this method will be called for all methods in this -// CompilationUnit, including inner class methods */ -// logger.info("method:"+n.getName()); -// super.visit(n, arg); -// } -// -// @Override -// public void visit(ClassOrInterfaceDeclaration n, Void arg) { -// logger.info("class:"+n.getName()+" extends:"+n.getExtendedTypes()+" implements:"+n.getImplementedTypes()); -// -// super.visit(n, arg); -// } -// -// @Override -// public void visit(PackageDeclaration n, Void arg) { -// logger.info("package:"+n.getName()); -// super.visit(n, arg); -// } -// -// } -//} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java new file mode 100644 index 00000000..2cd5dba6 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractCheckMojo.java @@ -0,0 +1,216 @@ +package com.jd.blockchain.contract.maven; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.apache.maven.shared.invoker.*; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +@Mojo(name = "contractCheck") +public class ContractCheckMojo extends AbstractMojo { + + Logger LOG = LoggerFactory.getLogger(ContractCheckMojo.class); + + public static final String CONTRACT_VERIFY = "contractVerify"; + + private static final String CONTRACT_MAVEN_PLUGIN = "contract-maven-plugin"; + + private static final String MAVEN_ASSEMBLY_PLUGIN = "maven-assembly-plugin"; + + private static final String JDCHAIN_PACKAGE = "com.jd.blockchain"; + + private static final String APACHE_MAVEN_PLUGINS = "org.apache.maven.plugins"; + + private static final String GOALS_VERIFY = "package"; + + private static final String GOALS_PACKAGE = "package"; + + private static final String OUT_POM_XML = "ContractPom.xml"; + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * jar's name; + */ + @Parameter + private String finalName; + + /** + * mainClass; + */ + @Parameter + private String mainClass; + /** + * ledgerVersion; + */ + @Parameter + private String ledgerVersion; + + /** + * mvnHome; + */ + @Parameter + private String mvnHome; + + /** + * first compile the class, then parse it; + */ + @Override + public void execute() throws MojoFailureException { + compileFiles(); + } + + private void compileFiles() throws MojoFailureException { + try (FileInputStream fis = new FileInputStream(project.getFile())) { + + MavenXpp3Reader reader = new MavenXpp3Reader(); + Model model = reader.read(fis); + + //delete this plugin(contractCheck) from destination pom.xml;then add the proper plugins; + Plugin plugin = model.getBuild().getPluginsAsMap() + .get(JDCHAIN_PACKAGE + ":" + CONTRACT_MAVEN_PLUGIN); + if(plugin == null){ + plugin = model.getBuild().getPluginsAsMap() + .get(APACHE_MAVEN_PLUGINS + ":" + CONTRACT_MAVEN_PLUGIN); + } + + if(plugin == null) { + return; + } + + model.getBuild().removePlugin(plugin); + + List plugins = new ArrayList<>(); + plugins.add(createAssembly()); + plugins.add(createContractVerify()); + + model.getBuild().setPlugins(plugins); + + handle(model); + + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new MojoFailureException(e.getMessage()); + } + } + + private void invokeCompile(File file) { + InvocationRequest request = new DefaultInvocationRequest(); + + Invoker invoker = new DefaultInvoker(); + try { + request.setPomFile(file); + + request.setGoals(Collections.singletonList(GOALS_VERIFY)); + invoker.setMavenHome(new File(mvnHome)); + invoker.execute(request); + } catch (MavenInvocationException e) { + LOG.error(e.getMessage()); + throw new IllegalStateException(e); + } + } + + private Plugin createContractVerify() { + Plugin plugin = new Plugin(); + plugin.setGroupId(JDCHAIN_PACKAGE); + plugin.setArtifactId(CONTRACT_MAVEN_PLUGIN); + plugin.setVersion(ledgerVersion); + + Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); + finalNameNode.setValue(finalName); + Xpp3Dom configuration = new Xpp3Dom("configuration"); + configuration.addChild(finalNameNode); + + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_VERIFY, CONTRACT_VERIFY)); + + return plugin; + } + + private Plugin createAssembly() { + Plugin plugin = new Plugin(); + plugin.setArtifactId(MAVEN_ASSEMBLY_PLUGIN); + + Xpp3Dom configuration = new Xpp3Dom("configuration"); + + Xpp3Dom mainClassNode = new Xpp3Dom("mainClass"); + mainClassNode.setValue(mainClass); + + Xpp3Dom manifest = new Xpp3Dom("manifest"); + manifest.addChild(mainClassNode); + + Xpp3Dom archive = new Xpp3Dom("archive"); + archive.addChild(manifest); + + Xpp3Dom finalNameNode = new Xpp3Dom("finalName"); + finalNameNode.setValue(finalName); + + Xpp3Dom appendAssemblyId = new Xpp3Dom("appendAssemblyId"); + appendAssemblyId.setValue("false"); + + Xpp3Dom descriptorRef = new Xpp3Dom("descriptorRef"); + descriptorRef.setValue("jar-with-dependencies"); + Xpp3Dom descriptorRefs = new Xpp3Dom("descriptorRefs"); + descriptorRefs.addChild(descriptorRef); + + configuration.addChild(finalNameNode); + configuration.addChild(appendAssemblyId); + configuration.addChild(archive); + configuration.addChild(descriptorRefs); + + plugin.setConfiguration(configuration); + plugin.setExecutions(pluginExecution("make-assembly", GOALS_PACKAGE, "single")); + + return plugin; + } + + private List pluginExecution(String id, String phase, String goal) { + PluginExecution pluginExecution = new PluginExecution(); + pluginExecution.setId(id); + pluginExecution.setPhase(phase); + List goals = new ArrayList<>(); + goals.add(goal); + pluginExecution.setGoals(goals); + List pluginExecutions = new ArrayList<>(); + pluginExecutions.add(pluginExecution); + + return pluginExecutions; + } + + private void handle(Model model) throws IOException { + + MavenXpp3Writer mavenXpp3Writer = new MavenXpp3Writer(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + mavenXpp3Writer.write(outputStream, model); + + byte[] buffer = outputStream.toByteArray(); + + File outPom = new File(project.getBasedir().getPath(), OUT_POM_XML); + + FileUtils.writeByteArrayToFile(outPom, buffer); + + invokeCompile(outPom); + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java new file mode 100644 index 00000000..10aada08 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployExeUtil.java @@ -0,0 +1,176 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.binaryproto.DataContractRegistry; +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.*; +import com.jd.blockchain.sdk.BlockchainService; +import com.jd.blockchain.sdk.client.GatewayServiceFactory; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.Bytes; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.net.NetworkAddress; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @Author zhaogw + * @Date 2018/11/2 10:18 + */ +public enum ContractDeployExeUtil { + instance; + private BlockchainService bcsrv; + private Bytes contractAddress; + + public BlockchainKeypair getKeyPair(String pubPath, String prvPath, String rawPassword){ + PubKey pub = null; + PrivKey prv = null; + try { + prv = KeyGenCommand.readPrivKey(prvPath, KeyGenCommand.encodePassword(rawPassword)); + pub = KeyGenCommand.readPubKey(pubPath); + + } catch (Exception e) { + e.printStackTrace(); + } + + return new BlockchainKeypair(pub, prv); + } + + public PubKey getPubKey(String pubPath){ + PubKey pub = null; + try { + if(pubPath == null){ + BlockchainKeypair contractKeyPair = BlockchainKeyGenerator.getInstance().generate(); + pub = contractKeyPair.getPubKey(); + }else { + pub = KeyGenCommand.readPubKey(pubPath); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return pub; + } + public byte[] getChainCode(String path){ + byte[] chainCode = null; + File file = null; + InputStream input = null; + try { + file = new File(path); + input = new FileInputStream(file); + chainCode = new byte[input.available()]; + input.read(chainCode); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if(input!=null){ + input.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return chainCode; + } + + private void register(){ + DataContractRegistry.register(TransactionContent.class); + DataContractRegistry.register(TransactionContentBody.class); + DataContractRegistry.register(TransactionRequest.class); + DataContractRegistry.register(NodeRequest.class); + DataContractRegistry.register(EndpointRequest.class); + DataContractRegistry.register(TransactionResponse.class); + DataContractRegistry.register(DataAccountKVSetOperation.class); + DataContractRegistry.register(DataAccountKVSetOperation.KVWriteEntry.class); + DataContractRegistry.register(Operation.class); + DataContractRegistry.register(ContractCodeDeployOperation.class); + DataContractRegistry.register(ContractEventSendOperation.class); + DataContractRegistry.register(DataAccountRegisterOperation.class); + DataContractRegistry.register(UserRegisterOperation.class); + } + + public BlockchainService initBcsrv(String host, int port) { + if(bcsrv!=null){ + return bcsrv; + } + NetworkAddress addr = new NetworkAddress(host, port); + GatewayServiceFactory gwsrvFact = GatewayServiceFactory.connect(addr); + bcsrv = gwsrvFact.getBlockchainService(); + return bcsrv; + } + + public boolean deploy(HashDigest ledgerHash, BlockchainIdentity contractIdentity, BlockchainKeypair ownerKey, byte[] chainCode){ + register(); + + TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); + txTpl.contracts().deploy(contractIdentity, chainCode); + PreparedTransaction ptx = txTpl.prepare(); + ptx.sign(ownerKey); + // 提交并等待共识返回; + TransactionResponse txResp = ptx.commit(); + + // 验证结果; + contractAddress = contractIdentity.getAddress(); + this.setContractAddress(contractAddress); + System.out.println("contract's address="+contractAddress); + return txResp.isSuccess(); + } + public boolean deploy(String host, int port, HashDigest ledgerHash, BlockchainKeypair ownerKey, byte[] chainCode){ + register(); + + BlockchainIdentity contractIdentity = BlockchainKeyGenerator.getInstance().generate().getIdentity(); + initBcsrv(host,port); + return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); + } + + // 根据用户指定的公钥生成合约地址 + public boolean deploy(String host, int port, String ledger,String ownerPubPath, String ownerPrvPath, + String ownerPassword, String chainCodePath,String pubPath){ + PubKey pubKey = getPubKey(pubPath); + BlockchainIdentity contractIdentity = new BlockchainIdentityData(pubKey); + byte[] chainCode = getChainCode(chainCodePath); + + BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); + HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); + initBcsrv(host,port); + return deploy(ledgerHash, contractIdentity, ownerKey, chainCode); + } + + +// 暂不支持从插件执行合约;此外,由于合约参数调用的格式发生变化,故此方法被废弃;by: huanghaiquan at 2019-04-30; + +// public boolean exeContract(String ledger,String ownerPubPath, String ownerPrvPath, +// String ownerPassword,String event,String contractArgs){ +// BlockchainKeypair ownerKey = getKeyPair(ownerPubPath, ownerPrvPath, ownerPassword); +// HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); +// +// // 定义交易,传输最简单的数字、字符串、提取合约中的地址; +// TransactionTemplate txTpl = bcsrv.newTransaction(ledgerHash); +// txTpl.contractEvents().send(getContractAddress(),event,contractArgs.getBytes()); +// +// // 签名; +// PreparedTransaction ptx = txTpl.prepare(); +// ptx.sign(ownerKey); +// +// // 提交并等待共识返回; +// TransactionResponse txResp = ptx.commit(); +// +// // 验证结果; +// return txResp.isSuccess(); +// } + + public Bytes getContractAddress() { + return contractAddress; + } + + public void setContractAddress(Bytes contractAddress) { + this.contractAddress = contractAddress; + } +} diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java new file mode 100644 index 00000000..25da2357 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractDeployMojo.java @@ -0,0 +1,119 @@ +package com.jd.blockchain.contract.maven; + +import com.jd.blockchain.crypto.HashDigest; +import com.jd.blockchain.crypto.PrivKey; +import com.jd.blockchain.crypto.PubKey; +import com.jd.blockchain.ledger.BlockchainKeypair; +import com.jd.blockchain.tools.keygen.KeyGenCommand; +import com.jd.blockchain.utils.StringUtils; +import com.jd.blockchain.utils.codec.Base58Utils; +import com.jd.blockchain.utils.io.FileUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * for contract remote deploy; + * @goal contractDeploy + * @phase process-sources + * @Author zhaogw + * @Date 2018/10/18 10:12 + */ + +@Mojo(name = "deploy") +public class ContractDeployMojo extends AbstractMojo { + Logger logger = LoggerFactory.getLogger(ContractDeployMojo.class); + + @Parameter + private File config; + + @Override + public void execute()throws MojoFailureException { + Properties prop = new Properties(); + InputStream input = null; + + try { + input = new FileInputStream(config); + prop.load(input); + + } catch (IOException ex) { + logger.error(ex.getMessage()); + throw new MojoFailureException("io error"); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + int port; + try { + port = Integer.parseInt(prop.getProperty("port")); + }catch (NumberFormatException e){ + logger.error(e.getMessage()); + throw new MojoFailureException("invalid port"); + } + String host = prop.getProperty("host"); + String ledger = prop.getProperty("ledger"); + String pubKey = prop.getProperty("pubKey"); + String prvKey = prop.getProperty("prvKey"); + String password = prop.getProperty("password"); + String contractPath = prop.getProperty("contractPath"); + + + if(StringUtils.isEmpty(host)){ + logger.info("host不能为空"); + return; + } + + if(StringUtils.isEmpty(ledger)){ + logger.info("ledger不能为空."); + return; + } + if(StringUtils.isEmpty(pubKey)){ + logger.info("pubKey不能为空."); + return; + } + if(StringUtils.isEmpty(prvKey)){ + logger.info("prvKey不能为空."); + return; + } + if(StringUtils.isEmpty(contractPath)){ + logger.info("contractPath不能为空."); + return; + } + + File contract = new File(contractPath); + if (!contract.isFile()){ + logger.info("文件"+contractPath+"不存在"); + return; + } + byte[] contractBytes = FileUtils.readBytes(contractPath); + + + PrivKey prv = KeyGenCommand.decodePrivKeyWithRawPassword(prvKey, password); + PubKey pub = KeyGenCommand.decodePubKey(pubKey); + BlockchainKeypair blockchainKeyPair = new BlockchainKeypair(pub, prv); + HashDigest ledgerHash = new HashDigest(Base58Utils.decode(ledger)); + + StringBuffer sb = new StringBuffer(); + sb.append("host:"+ host).append(",port:"+port).append(",ledgerHash:"+ledgerHash.toBase58()). + append(",pubKey:"+pubKey).append(",prvKey:"+prv).append(",contractPath:"+contractPath); + logger.info(sb.toString()); + ContractDeployExeUtil.instance.deploy(host,port,ledgerHash, blockchainKeyPair, contractBytes); + } + +} + + diff --git a/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java new file mode 100644 index 00000000..2cafd249 --- /dev/null +++ b/source/contract/contract-maven-plugin/src/main/java/com/jd/blockchain/contract/maven/ContractVerifyMojo.java @@ -0,0 +1,433 @@ +package com.jd.blockchain.contract.maven; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.jd.blockchain.contract.ContractType; +import org.apache.commons.io.FileUtils; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static com.jd.blockchain.contract.maven.ContractCheckMojo.CONTRACT_VERIFY; +import static com.jd.blockchain.utils.decompiler.utils.DecompilerUtils.decompileJarFile; +import static com.jd.blockchain.utils.jar.ContractJarUtils.*; + +/** + * first step, we want to parse the source code by javaParse. But it's repeated and difficult to parse the source. + * This is a try of "from Initail to Abandoned". + * Since we are good at the class, why not? + * Now we change a way of thinking, first we pre-compile the source code, then parse the *.jar. + * + * by zhaogw + * date 2019-06-05 16:17 + */ +@Mojo(name = CONTRACT_VERIFY) +public class ContractVerifyMojo extends AbstractMojo { + + Logger LOG = LoggerFactory.getLogger(ContractVerifyMojo.class); + + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * jar's name; + */ + @Parameter + private String finalName; + + private static final String JAVA_SUFFIX = ".java"; + + private static final String PATH_DIRECT = + "src" + File.separator + + "main" + File.separator + + "java" + File.separator; + + private static final String CONFIG = "config.properties"; + + private static final String BLACK_PACKAGE_LIST = "black.package.list"; + + private static final String BLACK_CLASS_LIST = "black.class.list"; + + private static final String BLACK_NAME_LIST = "black.name.list"; + + @Override + public void execute() throws MojoExecutionException { + + try { + + File jarFile = copyAndManage(); + + // 首先校验MainClass + verifyMainClass(jarFile); + + Properties config = loadConfig(); + + List blackNameList = blackNameList(config); + + List blackPackageList = blackPackageList(config); + + Set blackClassSet = blackClassSet(config); + + LinkedList totalClassList = loadAllClass(jarFile); + // 该项目路径 + String projectDir = project.getBasedir().getPath(); + // 代码路径 + String codeBaseDir = projectDir + File.separator + PATH_DIRECT; + + if (!totalClassList.isEmpty()) { + + boolean isOK = true; + + for (String clazz : totalClassList) { + // 获取其包名 + String packageName = packageName(clazz); + + // 包的名字黑名单,不能打包该类进入Jar包中,或者合约不能命名这样的名字 + boolean isNameBlack = false; + for (ContractPackage blackName : blackNameList) { + isNameBlack = verifyPackage(packageName, blackName); + if (isNameBlack) { + break; + } + } + + // 假设是黑名单则打印日志 + if (isNameBlack) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s Package-Name belong to BlackNameList !!!", clazz)); + isOK = false; + continue; + } + + // 获取该Class对应的Java文件 + File javaFile = new File(codeBaseDir + clazz + JAVA_SUFFIX); + + boolean isNeedDelete = false; + if (!javaFile.exists()) { + // 表明不是项目中的内容,需要通过反编译获取该文件 + String source = null; + try { + source = decompileJarFile(jarFile.getPath(), clazz, true, StandardCharsets.UTF_8.name()); + if (source == null || source.length() == 0) { + throw new IllegalStateException(); + } + } catch (Exception e) { + LOG.warn(String.format("Decompile Jar[%s]->Class[%s] Fail !!!", jarFile.getPath(), clazz)); + } + // 将source写入Java文件 + File sourceTempJavaFile = new File(tempPath(codeBaseDir, clazz)); + FileUtils.writeStringToFile(sourceTempJavaFile, source == null ? "" : source); + javaFile = sourceTempJavaFile; + isNeedDelete = true; + } + + // 解析文件中的内容 + CompilationUnit compilationUnit = JavaParser.parse(javaFile); + + MethodVisitor methodVisitor = new MethodVisitor(); + + compilationUnit.accept(methodVisitor, null); + + List imports = methodVisitor.importClasses; + + if (!imports.isEmpty()) { + for (String importClass : imports) { + if (importClass.endsWith("*")) { + // 导入的是包 + for (ContractPackage blackPackage : blackPackageList) { + String importPackageName = importClass.substring(0, importClass.length() - 2); + if (verifyPackage(importPackageName, blackPackage)) { + // 打印信息供检查 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } else { + // 导入的是具体的类,则判断类黑名单 + 包黑名单 + if (blackClassSet.contains(importClass)) { + // 包含导入类,该方式无法通过验证 + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackClassList !!!", clazz, importClass)); + isOK = false; + } else { + // 判断导入的该类与黑名单导入包的对应关系 + for (ContractPackage blackPackage : blackPackageList) { + if (verifyClass(importClass, blackPackage)) { + LOG.error(String.format("Class[%s]'s import class [%s] belong to BlackPackageList !!!", clazz, importClass)); + isOK = false; + break; + } + } + } + } + } + } + if (isNeedDelete) { + javaFile.delete(); + } + } + if (!isOK) { + throw new IllegalStateException("There are many Illegal information, please check !!!"); + } + } else { + throw new IllegalStateException("There is none class !!!"); + } + } catch (Exception e) { + LOG.error(e.getMessage()); + throw new MojoExecutionException(e.getMessage()); + } + } + + private void verifyMainClass(File jarFile) throws Exception { + // 加载main-class,开始校验类型 + URL jarURL = jarFile.toURI().toURL(); + ClassLoader classLoader = new URLClassLoader(new URL[]{jarURL}, this.getClass().getClassLoader()); + Attributes m = new JarFile(jarFile).getManifest().getMainAttributes(); + String contractMainClass = m.getValue(Attributes.Name.MAIN_CLASS); + Class mainClass = classLoader.loadClass(contractMainClass); + ContractType.resolve(mainClass); + } + + private List blackNameList(Properties config) { + return blackList(config, BLACK_NAME_LIST); + } + + private Set blackClassSet(Properties config) { + Set blackClassSet = new HashSet<>(); + String attrProp = config.getProperty(BLACK_CLASS_LIST); + if (attrProp != null && attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + blackClassSet.add(attr.trim()); + } + } + return blackClassSet; + } + + private List blackPackageList(Properties config) { + return blackList(config, BLACK_PACKAGE_LIST); + } + + private List blackList(Properties config, String attrName) { + List list = new ArrayList<>(); + String attrProp = config.getProperty(attrName); + if (attrProp != null || attrProp.length() > 0) { + String[] attrPropArray = attrProp.split(","); + for (String attr : attrPropArray) { + list.add(new ContractPackage(attr)); + } + } + return list; + } + + private boolean verifyPackage(String packageName, ContractPackage contractPackage) { + boolean verify = false; + if (packageName.equals(contractPackage.packageName)) { + // 完全相同 + verify = true; + } else if (packageName.startsWith(contractPackage.packageName) && + contractPackage.isTotal) { + // 以某个包开头 + verify = true; + } + return verify; + } + + private boolean verifyClass(String className, ContractPackage contractPackage) { + boolean verify = false; + + if (contractPackage.isTotal) { + // 表示该包下面的其他所有包都会受限制,此处需要判断起始 + if (className.startsWith(contractPackage.packageName)) { + verify = true; + } + } else { + // 表示该包必须完整匹配ClassName所在包 + // 获取ClassName所在包 + String packageName = packageNameByDot(className); + if (packageName.equals(contractPackage.packageName)) { + verify = true; + } + } + return verify; + } + + private String packageNameByDot(String className) { + String[] array = className.split("."); + if (Character.isLowerCase(array[array.length - 2].charAt(0))) { + // 如果是小写,表示非内部类 + // 获取完整包名 + return className.substring(0, className.lastIndexOf(".")); + } + // 表示为内部类,该包拼装组成 + StringBuilder buffer = new StringBuilder(); + for (String s : array) { + if (buffer.length() > 0) { + buffer.append("."); + } + if (Character.isUpperCase(s.charAt(0))) { + // 表明已经到具体类 + break; + } + buffer.append(s); + } + + if (buffer.length() == 0) { + throw new IllegalStateException(String.format("Import Class [%s] Illegal !!!", className)); + } + + return buffer.toString(); + } + + private String packageName(String clazz) { + int index = clazz.lastIndexOf("/"); + String packageName = clazz.substring(0, index); + return packageName.replaceAll("/", "."); + } + + private File copyAndManage() throws IOException { + // 首先将Jar包转换为指定的格式 + String srcJarPath = project.getBuild().getDirectory() + + File.separator + finalName + ".jar"; + + String dstJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-temp-" + System.currentTimeMillis() + ".jar"; + + File srcJar = new File(srcJarPath), dstJar = new File(dstJarPath); + + // 首先进行Copy处理 + copy(srcJar, dstJar); + + byte[] txtBytes = contractMF(FileUtils.readFileToByteArray(dstJar)).getBytes(StandardCharsets.UTF_8); + + String finalJarPath = project.getBuild().getDirectory() + + File.separator + finalName + "-jdchain.jar"; + + File finalJar = new File(finalJarPath); + + copy(dstJar, finalJar, contractMFJarEntry(), txtBytes, null); + + // 删除临时文件 + FileUtils.forceDelete(dstJar); + + return finalJar; + } + + private Properties loadConfig() throws Exception { + + Properties properties = new Properties(); + + properties.load(this.getClass().getClassLoader().getResourceAsStream(CONFIG)); + + return properties; + } + + private LinkedList loadAllClass(File file) throws Exception { + JarFile jarFile = new JarFile(file); + LinkedList allClass = new LinkedList<>(); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + if (entryName.endsWith(".class")) { + // 内部类,不需要处理 + if (!entryName.contains("$")) { + allClass.addLast(entryName.substring(0, entryName.length() - 6)); + } + } + } + return allClass; + } + + private String tempPath(String codeBaseDir, String clazz) { + // 获取最后的名称 + String[] classArray = clazz.split("/"); + String tempPath = codeBaseDir + classArray[classArray.length - 1] + "_" + + System.currentTimeMillis() + "_" + System.nanoTime() + JAVA_SUFFIX; + return tempPath; + } + + private static class MethodVisitor extends VoidVisitorAdapter { + + private List importClasses = new ArrayList<>(); + +// @Override +// public void visit(MethodDeclaration n, Void arg) { +// /* here you can access the attributes of the method. +// this method will be called for all methods in this +// CompilationUnit, including inner class methods */ +// super.visit(n, arg); +// } +// +// @Override +// public void visit(ClassOrInterfaceDeclaration n, Void arg) { +// super.visit(n, arg); +// } +// +// @Override +// public void visit(PackageDeclaration n, Void arg) { +// super.visit(n, arg); +// } + + @Override + public void visit(ImportDeclaration n, Void arg) { + importClasses.add(parseClass(n.toString())); + super.visit(n, arg); + } + + private String parseClass(String importInfo) { + String className = importInfo.substring(7, importInfo.length() - 2); + if (importInfo.startsWith("import static ")) { + // 获取静态方法的类信息 + className = importInfo.substring(14, importInfo.lastIndexOf(".")); + } + if (!className.contains(".")) { + throw new IllegalStateException(String.format("Import Class [%s] is Illegal !!", className)); + } + return className; + } + } + + private static class ContractPackage { + + private String packageName; + + private boolean isTotal = false; + + public ContractPackage() { + } + + public ContractPackage(String totalPackage) { + if (totalPackage.endsWith("*")) { + this.packageName = totalPackage.substring(0, totalPackage.length() - 2).trim(); + this.isTotal = true; + } else { + this.packageName = totalPackage; + } + } + + public String getPackageName() { + return packageName; + } + + public boolean isTotal() { + return isTotal; + } + } +} diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java index 0d205758..442f1ba0 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractDeployMojoTest.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractDeployMojo; +import com.jd.blockchain.contract.maven.ContractDeployMojo; import java.lang.reflect.Field; diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java index ac9813cd..570cc8e6 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyMojoTest.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractVerifyMojo; +import com.jd.blockchain.contract.maven.ContractVerifyMojo; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.junit.Test; import org.slf4j.Logger; @@ -21,7 +21,7 @@ public class ContractVerifyMojoTest extends AbstractMojoTestCase { assertNotNull( pom ); assertTrue( pom.exists() ); - ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "Contract.Verify", pom ); + ContractVerifyMojo myMojo = (ContractVerifyMojo) lookupMojo( "contractVerify", pom ); assertNotNull( myMojo ); myMojo.execute(); } diff --git a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java similarity index 93% rename from source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java rename to source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java index d57ab5d0..f3e8fea9 100644 --- a/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest.java +++ b/source/contract/contract-maven-plugin/src/test/java/com/jd/blockchain/ledger/ContractVerifyTest_.java @@ -1,6 +1,6 @@ package com.jd.blockchain.ledger; -import com.jd.blockchain.ContractVerifyMojo; +import com.jd.blockchain.contract.maven.ContractVerifyMojo; import org.apache.maven.project.MavenProject; import org.junit.Before; import org.junit.Test; @@ -9,7 +9,7 @@ import java.lang.reflect.Field; import static com.jd.blockchain.ledger.ContractTestBase.mavenProjectInit; -public class ContractVerifyTest { +public class ContractVerifyTest_ { private MavenProject project;