@@ -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<Path> 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<ImportDeclaration> 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<Void> { | |||
// @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); | |||
// } | |||
// | |||
// } | |||
//} |
@@ -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<Plugin> 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> pluginExecution(String id, String phase, String goal) { | |||
PluginExecution pluginExecution = new PluginExecution(); | |||
pluginExecution.setId(id); | |||
pluginExecution.setPhase(phase); | |||
List<String> goals = new ArrayList<>(); | |||
goals.add(goal); | |||
pluginExecution.setGoals(goals); | |||
List<PluginExecution> 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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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<ContractPackage> blackNameList = blackNameList(config); | |||
List<ContractPackage> blackPackageList = blackPackageList(config); | |||
Set<String> blackClassSet = blackClassSet(config); | |||
LinkedList<String> 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<String> 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<ContractPackage> blackNameList(Properties config) { | |||
return blackList(config, BLACK_NAME_LIST); | |||
} | |||
private Set<String> blackClassSet(Properties config) { | |||
Set<String> 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<ContractPackage> blackPackageList(Properties config) { | |||
return blackList(config, BLACK_PACKAGE_LIST); | |||
} | |||
private List<ContractPackage> blackList(Properties config, String attrName) { | |||
List<ContractPackage> 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<String> loadAllClass(File file) throws Exception { | |||
JarFile jarFile = new JarFile(file); | |||
LinkedList<String> allClass = new LinkedList<>(); | |||
Enumeration<JarEntry> 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<Void> { | |||
private List<String> 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; | |||
} | |||
} | |||
} |
@@ -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; | |||
@@ -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(); | |||
} | |||
@@ -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; | |||