blob: aa5efcc04827774af701525f0f91a72827730ca5 [file] [log] [blame]
/*
* Copyright (c) Robert Bosch GmbH. All rights reserved.
*/
package org.eclipse.blockchain.core;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.eclipse.blockchain.core.log.EthereumLogService;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.internal.loader.EquinoxClassLoader;
import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Code inside this class is just a prototype so it could contain duplicate/in-efficient code and maybe unwanted code
* SOLDITY COMPILATION IS TESTED WITH SOLC-0.5.4 version as of NOW
*
* @author ADG5COB
*/
public class CoreCommandExecutor {
private Process gethServer = null;
/**
* As of now only one Geth server instance is considered so everything related to geth is maintained as member
* variables
*/
private String gethIPC = "";
private String gethDataDirectory = "";
private Map<String, String> gethOptions = null;
private final String solcCommand = "solc-path solidity-file --bin --abi --optimize -o output-directory";
private final String web3jCommand =
"web3j-path solidity generate -a=abi-path -b=bin-path -o=output-directory -p=com.bosch";
private final Map<String, SolidityABI[]> solidityABIMap = new HashMap<>();
private static CoreCommandExecutor instance;
private String gethServerStartError = "";
/**
* This instance is used to handle functionality related to geth and solidity
*
* @return - CorecommandExecutor instance
*/
public static CoreCommandExecutor getInstance() {
if (instance == null) {
instance = new CoreCommandExecutor();
}
return instance;
}
String getGethServerStartError() {
return this.gethServerStartError;
}
/**
* To start/stop mining process
*
* @param start - if true mining will start/ if false mining will stop
* @throws IOException -
* @throws InterruptedException -
*/
public void mine(final boolean start) throws IOException, InterruptedException {
Process miner = Runtime.getRuntime().exec("cmd");
StringJoiner inputStream = new StringJoiner(System.lineSeparator());
StringJoiner errorStream = new StringJoiner(System.lineSeparator());
cmdRead(miner, inputStream);
cmdReadError(miner, errorStream);
PrintWriter writer = new PrintWriter(miner.getOutputStream());
if (start) {
writer.println("geth attach " + this.gethIPC.replace("\\\\", "\\"));
writer.println("miner.start(3)");
writer.close();
miner.waitFor();
miner.destroy();
}
else {
writer.println("geth attach " + this.gethIPC.replace("\\\\", "\\"));
writer.println("miner.stop()");
writer.close();
miner.waitFor();
miner.destroy();
}
}
private void transferArtifactsFromTempToOutput(final String tempLocation, final String outputLocation,
final String solName)
throws IOException {
// ABI file
File abiTempFile = new File(tempLocation + File.separator + solName + ".abi");
File abiFile = new File(outputLocation + File.separator + solName + ".abi");
abiFile.createNewFile();
FileUtils.copyFile(abiTempFile, abiFile);
// BIN file
File binTempFile = new File(tempLocation + File.separator + solName + ".bin");
File binFile = new File(outputLocation + File.separator + solName + ".bin");
abiFile.createNewFile();
FileUtils.copyFile(binTempFile, binFile);
}
/**
* Some osgi internal API are accessed in this method to retrieve the classpath content of this plugin. It won't be
* changed until problems pop-up
*
* @param projectName -
* @param solidityFiles -
* @param outputDir -
* @return -
* @throws IOException -
* @throws InterruptedException -
* @throws ClassNotFoundException -
*/
public String solidityCompile(final String projectName, final List<IResource> solidityFiles,
final IResource outputDir)
throws IOException, InterruptedException, ClassNotFoundException {
String solcCompilerPath = InstanceScope.INSTANCE.getNode(SolidityPreferenceConstants.PREF_NODE)
.get(SolidityPreferenceConstants.PREF_KEY, "");
if (solcCompilerPath.isEmpty()) {
return "Compiler is not set... Please set it in solidity preference page";
}
for (IResource solFile : solidityFiles) {
String tempLocation = outputDir.getLocation().toOSString() + File.separator + "temp";
File tempOutputDir = new File(tempLocation);
if (tempOutputDir.exists() && tempOutputDir.isDirectory()) {
tempOutputDir.delete();
}
tempOutputDir.mkdir();
String compileCommand =
this.solcCommand.replace("solc-path", getCmdLinePath(solcCompilerPath + File.separator + "solc"))
.replace("solidity-file", getCmdLinePath(solFile.getLocation().toOSString()))
.replace("output-directory", getCmdLinePath(tempOutputDir.getAbsolutePath()));
Process solcExe = Runtime.getRuntime().exec("cmd");
StringJoiner inputStream = new StringJoiner(System.lineSeparator());
StringJoiner errorStream = new StringJoiner(System.lineSeparator());
cmdRead(solcExe, inputStream);
cmdReadError(solcExe, errorStream);
PrintWriter writer = new PrintWriter(solcExe.getOutputStream());
writer.println(compileCommand);
writer.close();
solcExe.waitFor();
solcExe.destroy();
if (inputStream.toString().contains("Error") || errorStream.toString().contains("Error")) {
return "Some errors occurred during compilation please check the console!!!";
}
transferArtifactsFromTempToOutput(tempOutputDir.getAbsolutePath(), outputDir.getLocation().toOSString(),
solFile.getName().replace(".sol", ""));
// Reading abi json
storeABIinSolidityMap(
outputDir.getLocation().toOSString() + File.separator + solFile.getName().replace(".sol", ".abi"),
solFile.getLocation().toOSString());
// Solidity ABI javaType update
CoreCommandExecutor.getInstance().updateSolABIMap(solFile.getLocation().toOSString(), updateSolJavaTypes(
projectName, CoreCommandExecutor.getInstance().getSolABIMap(solFile.getLocation().toOSString())));
// Java Wrapper Creation
String web3jCmd = this.web3jCommand
.replace("web3j-path", getCmdLinePath(unzipAndGetWeb3jPath()) + File.separator + "web3j")
.replace("abi-path",
getCmdLinePath(
outputDir.getLocation().toOSString() + File.separator + solFile.getName().replace(".sol", ".abi")))
.replace("bin-path",
getCmdLinePath(
outputDir.getLocation().toOSString() + File.separator + solFile.getName().replace(".sol", ".bin")))
.replace("output-directory", getCmdLinePath(outputDir.getLocation().toOSString()));
EthereumLogService.INSTANCE.log("web3j command -> " + web3jCmd);
Process web3jExe = Runtime.getRuntime().exec("cmd");
cmdRead(web3jExe, inputStream);
cmdReadError(web3jExe, errorStream);
writer = new PrintWriter(web3jExe.getOutputStream());
writer.println("set JAVA_HOME=" + getJDKPath());
writer.println(web3jCmd);
ClassLoader classLoader = CoreCommandExecutor.class.getClassLoader();
ClasspathEntry[] hostClasspathEntries =
((EquinoxClassLoader) classLoader).getClasspathManager().getHostClasspathEntries();
String cp = "";
for (ClasspathEntry c : hostClasspathEntries) {
cp = cp + c.getBundleFile().getBaseFile().toString() + ";";
}
writer
.println(
"javac " +
getCmdLinePath(outputDir.getLocation().toOSString() + File.separator + "com" + File.separator +
"bosch" + File.separator + solFile.getName().replace(".sol", ".java")) +
" -cp " + getCmdLinePath(cp));
writer.close();
web3jExe.waitFor();
web3jExe.destroy();
ProjectCreator.getInstance().getEthProject(projectName).addCompiledSolidityFiles(solFile);
FileUtils.deleteDirectory(tempOutputDir);
}
return "";
}
private String getCmdLinePath(final String path) {
return "\"" + path + "\"";
}
private SolidityABI[] updateSolJavaTypes(final String projectName, final SolidityABI[] solABI)
throws ClassNotFoundException, IOException, InterruptedException {
SolidityABI[] updatedSolABI = new SolidityABI[solABI.length];
for (int i = 0; i < solABI.length; i++) {
updatedSolABI[i] = solABI[i];
if (updatedSolABI[i].getInputs() != null) {
for (IOABI ioa : updatedSolABI[i].getInputs()) {
SolidityDynamicValueCasterHandler.assignJavaType(projectName,
SolidityToJavaDataTypeResolver.getInstance().getJavaType(ioa.getType(), ioa), ioa);
}
}
if (updatedSolABI[i].getOutputs() != null) {
for (IOABI ioa : updatedSolABI[i].getOutputs()) {
SolidityDynamicValueCasterHandler.assignJavaType(projectName,
SolidityToJavaDataTypeResolver.getInstance().getJavaType(ioa.getType(), ioa), ioa);
}
}
}
return updatedSolABI;
}
private String unzipAndGetWeb3jPath() throws IOException {
Bundle bundle = Platform.getBundle("org.eclipse.blockchain.core");
return unzipWeb3jZip(FileLocator.toFileURL(bundle.getEntry("res/web3j-4.3.0.zip")).getPath());
}
private String unzipWeb3jZip(final String zipPath) throws IOException {
ZipFile web3jZip = new ZipFile(zipPath);
String destinationDir = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString();
File web3jZipContainer = new File(destinationDir + File.separator + "web3jContainer");
if (!web3jZipContainer.exists()) {
web3jZipContainer.mkdir();
final byte[] buf = new byte[4096];
final Enumeration<? extends ZipEntry> zipEnum = web3jZip.entries();
while (zipEnum.hasMoreElements()) {
final ZipEntry item = zipEnum.nextElement();
if (item.isDirectory()) {
final File newdir = new File(web3jZipContainer + File.separator + item.getName());
newdir.mkdir();
}
else {
final String newfilePath = web3jZipContainer + File.separator + item.getName();
final File newFile = new File(newfilePath);
if (!newFile.getParentFile().exists()) {
newFile.getParentFile().mkdirs();
}
try (InputStream is = web3jZip.getInputStream(item);
FileOutputStream fos = new FileOutputStream(newfilePath);) {
int len;
while ((len = is.read(buf)) > 0) {
fos.write(buf, 0, len);
}
is.close();
fos.close();
}
}
}
}
web3jZip.close();
return web3jZipContainer.getAbsolutePath() + File.separator + "web3j-4.3.0" + File.separator + "bin";
}
/**
* @return -
* @throws IOException -
* @throws InterruptedException -
*/
public String getJDKPath() throws IOException, InterruptedException {
String jdkPath = "";
Process process = Runtime.getRuntime().exec("cmd");
PrintWriter writer = new PrintWriter(process.getOutputStream());
StringJoiner readJDKPath = new StringJoiner(System.lineSeparator());
cmdRead(process, readJDKPath);
writer.println("where javac");
writer.close();
process.waitFor();
process.destroy();
String[] lines = readJDKPath.toString().split(System.lineSeparator());
for (String line : lines) {
if (line.contains("javac.exe")) {
jdkPath = line.replace("\\bin\\javac.exe", "").replace("\\", "\\\\");
break;
}
}
return getCmdLinePath(jdkPath);
}
private void storeABIinSolidityMap(final String abiPath, final String solidityFilePath) throws IOException {
String abiJson = readABI(abiPath);
ObjectMapper jsonMapper = new ObjectMapper();
SolidityABI[] readValue = jsonMapper.readValue(abiJson, SolidityABI[].class);
for (SolidityABI sabi : readValue) {
if (sabi.isPayable()) {
IOABI[] inputs = sabi.getInputs();
IOABI[] newInputs = new IOABI[inputs.length + 1];
for (int o = 0; o < inputs.length; o++) {
newInputs[o] = inputs[o];
}
IOABI payableInput = new IOABI();
payableInput.setName("payableAmount");
payableInput.setType("uint256");
newInputs[inputs.length] = payableInput;
sabi.setInputs(newInputs);
}
}
this.solidityABIMap.put(solidityFilePath, readValue);
}
/**
* @param solPath - The absolute path of solidity file
* @param onlyConstructor - If true will return only constructor ui components
* @return - The UI content to be constructed in transactions view
*/
public Map<String, String> getUIComponentForSolidityFile(final String solPath, final boolean onlyConstructor) {
SolidityABI[] solABi = this.solidityABIMap.get(solPath);
Map<String, String> uiMap = new HashMap<>();
if (solABi != null) {
for (SolidityABI sABI : solABi) {
if (sABI.getType().equalsIgnoreCase("constructor")) {
if (onlyConstructor) {
String key = "deploy";
IOABI[] inputs = sABI.getInputs();
String value = constructUIValue(inputs);
uiMap.put(key, value);
}
}
else if (sABI.getType().equalsIgnoreCase("function")) {
if (!onlyConstructor) {
String key = sABI.getName();
IOABI[] inputs = sABI.getInputs();
String value = constructUIValue(inputs);
uiMap.put(key + "-" + value, value);
}
}
}
}
return uiMap;
}
private String constructUIValue(final IOABI[] inputs) {
StringJoiner uiElemString = new StringJoiner(";");
for (IOABI input : inputs) {
uiElemString.add(input.getJavaType().getSimpleName());
}
return uiElemString.toString();
}
/**
* @param abiPath -
* @return -
*/
public SolidityABI[] getSolABIMap(final String abiPath) {
return this.solidityABIMap.get(abiPath);
}
/**
* @param abiPath -
* @param updatedSolABI -
*/
public void updateSolABIMap(final String abiPath, final SolidityABI[] updatedSolABI) {
this.solidityABIMap.put(abiPath, updatedSolABI);
}
private String readABI(final String abiPath) throws IOException {
StringJoiner sj = new StringJoiner("");
try (BufferedReader br = new BufferedReader(new FileReader(new File(abiPath)))) {
String line = "";
while ((line = br.readLine()) != null) {
sj.add(line);
}
}
return sj.toString();
}
/**
* @return -
* @throws IOException -
* @throws InterruptedException -
*/
public String isGethInstalled() throws IOException, InterruptedException {
/**
* This is not fully constructed but this should be first step check when someone tries to invoke ethereum project
* creation
*/
StringJoiner inputStream = new StringJoiner(System.lineSeparator());
StringJoiner errorStream = new StringJoiner(System.lineSeparator());
String gethCheck = "";
Process gethProcess = Runtime.getRuntime().exec("cmd");
cmdRead(gethProcess, inputStream);
cmdReadError(gethProcess, errorStream);
PrintWriter processWriter = new PrintWriter(gethProcess.getOutputStream());
processWriter.println("geth version");
processWriter.close();
gethProcess.waitFor();
gethCheck = errorStream.toString();
gethProcess.destroy();
return gethCheck;
}
/**
* @param dataDir -
* @param initFilePath -
* @param localGethOptions -
* @return - Return if any error occurs during server start
* @throws IOException -
* @throws InterruptedException
*/
public String startGethServer(final String dataDir, final String initFilePath,
final Map<String, String> localGethOptions)
throws IOException, InterruptedException {
if (isGethInstalled()
.equals(NLS.bind(CoreCommandMessages.FRAMEWORK_NOT_INSTALLED, "'geth'", System.lineSeparator()))) {
return "Either Geth is not installed (or) its reference is not set in PATH env variable";
}
this.gethServerStartError = "Geth";
StringJoiner inputStream = new StringJoiner(System.lineSeparator());
StringJoiner errorStream = new StringJoiner(System.lineSeparator());
this.gethDataDirectory = dataDir;
CoreCommandExecutor.getInstance().gethOptions = localGethOptions;
this.gethServer = Runtime.getRuntime().exec("cmd.exe");
cmdRead(this.gethServer, inputStream);
cmdReadError(this.gethServer, errorStream);
String initFile = initFilePath;
String genesisFilePath = dataDir + File.separator + "genesis.json";
if (new File(genesisFilePath).exists()) {
initFile = genesisFilePath;
}
else {
if (initFilePath.isEmpty()) {
initFile = createGethInitFile(dataDir);
}
}
startGethServerLocal(dataDir, initFile, localGethOptions);
return "";
}
private void startGethServerLocal(final String dataDir, final String initFile,
final Map<String, String> localGethOptions) {
PrintWriter processWriter = new PrintWriter(this.gethServer.getOutputStream());
if (!initFile.isEmpty() && !new File(dataDir + File.separator + "geth").exists()) {
processWriter.println("geth --datadir " + getCmdLinePath(dataDir) + " init " + getCmdLinePath(initFile) + "");
}
String args = constructGethArguments(localGethOptions);
processWriter.println("geth --datadir " + getCmdLinePath(dataDir) + " " + args);
processWriter.close();
/**
* This below code is required because geth once started won't terminate and it should not terminate because its a
* server
*/
Display.getDefault().syncExec(() -> {
new Thread(() -> {
try {
this.gethServer.waitFor();
}
catch (InterruptedException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}, "Geth Server").start();
});
}
/**
* @return - True if geth is started/ false otherwise
*/
public boolean isGethStarted() {
return (this.gethServer != null) && this.gethServer.isAlive() && !this.gethIPC.isEmpty();
}
/**
* @return - The geth server data directory
*/
public String getDataDir() {
return this.gethDataDirectory;
}
/**
* @return -The geth command line options
*/
public Map<String, String> getGethOptions() {
return this.gethOptions;
}
/**
* @return - Returns the default network id
*/
public String getDefaultNetworkIdForGeth() {
String defaultNetworkId = "";
try (BufferedReader br =
new BufferedReader(new InputStreamReader(CoreCommandExecutor.class.getResourceAsStream("genesis.template")));) {
String genesisContent = "";
while ((genesisContent = br.readLine()) != null) {
if (genesisContent.contains("chainId")) {
defaultNetworkId = genesisContent.substring(genesisContent.indexOf(':') + 1).replace(",", "").trim();
}
}
}
catch (IOException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
return defaultNetworkId;
}
private String constructGethArguments(final Map<String, String> localGethOptions) {
StringJoiner arguments = new StringJoiner(" ");
localGethOptions.forEach((k, v) -> arguments.add("--" + k + "" + (v.isEmpty() ? "" : " " + v)));
return arguments.toString();
}
/**
* Terminate geth server
*/
public void terminateGethServer() {
if (CoreCommandExecutor.getInstance().gethServer != null) {
this.gethIPC = "";
this.gethServer.destroyForcibly();
try {
Process terminator = Runtime.getRuntime().exec("cmd.exe");
PrintWriter pw = new PrintWriter(terminator.getOutputStream());
pw.println("taskkill /F /IM geth.exe");
pw.close();
terminator.waitFor();
terminator.destroy();
}
catch (IOException | InterruptedException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}
}
private String createGethInitFile(final String dataDir) throws IOException {
File gethInitFile = new File(dataDir + File.separator + "genesis.json");
gethInitFile.createNewFile();// NOSONAR
try (
BufferedReader br = new BufferedReader(
new InputStreamReader(CoreCommandExecutor.class.getResourceAsStream("genesis.template")));
BufferedWriter bw = new BufferedWriter(new FileWriter(gethInitFile));) {
String genesisContent = "";
while ((genesisContent = br.readLine()) != null) {
bw.append(genesisContent);
bw.append(System.lineSeparator());
}
}
return gethInitFile.getAbsolutePath();
}
/**
* Read output stream of process going to be executed
*
* @param process - The process that is going to start
* @param inputStream - A log recording object
*/
public void cmdRead(final Process process, final StringJoiner inputStream) {
Thread processReadThread = new Thread(() -> {
String line = "";
try (BufferedReader processReader = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
while ((line = processReader.readLine()) != null) {
inputStream.add(line.trim());
if (line.contains("Error") && this.gethServerStartError.equals("Geth")) {
this.gethServerStartError = line;
}
if (line.contains("IPC endpoint opened")) {// To determine IPC endpoint when geth server is started.
this.gethIPC = line.substring(line.indexOf("url=") + 4);
}
EthereumLogService.INSTANCE.log(line);
System.err.println(line);
}
}
catch (IOException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}, "Process Read Thread");
processReadThread.start();
}
/**
* @return - the geth socket handle
*/
public String getIPCPath() {
return this.gethIPC;
}
/**
* Read error stream of process going to be executed
*
* @param process - The process that is going to start
* @param errorStream - A log recording object
*/
public void cmdReadError(final Process process, final StringJoiner errorStream) {
Thread processErrorThread = new Thread(() -> {
String line = "";
try (BufferedReader processReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));) {
while ((line = processReader.readLine()) != null) {
errorStream.add(line.trim());
if (line.contains("Error") && this.gethServerStartError.equals("Geth")) {
this.gethServerStartError = line;
}
if (line.contains("IPC endpoint opened")) {// To determine IPC endpoint when geth server is started.
this.gethIPC = line.substring(line.indexOf("url=") + 4);
}
EthereumLogService.INSTANCE.errorLog(line);
System.err.println(line);
}
}
catch (IOException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}, "Process Error Thread");
processErrorThread.start();
}
}