/* | |
* 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.FileWriter; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.lang.reflect.Field; | |
import java.math.BigInteger; | |
import java.nio.file.FileVisitResult; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.nio.file.SimpleFileVisitor; | |
import java.nio.file.attribute.BasicFileAttributes; | |
import java.security.InvalidAlgorithmParameterException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.NoSuchProviderException; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.ExecutionException; | |
import org.apache.commons.io.FileUtils; | |
import org.eclipse.blockchain.classloader.DynamicClassLoader; | |
import org.eclipse.blockchain.core.events.BlockchainEnvironmentChangedTrigger; | |
import org.eclipse.blockchain.core.events.IBlockchainEnvironmentChangedEvent; | |
import org.eclipse.blockchain.core.events.IBlockchainEnvironmentChangedListener; | |
import org.eclipse.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.preferences.InstanceScope; | |
import org.web3j.abi.FunctionReturnDecoder; | |
import org.web3j.abi.datatypes.Address; | |
import org.web3j.abi.datatypes.Event; | |
import org.web3j.crypto.CipherException; | |
import org.web3j.crypto.Credentials; | |
import org.web3j.crypto.WalletUtils; | |
import org.web3j.evm.Configuration; | |
import org.web3j.evm.EmbeddedWeb3jService; | |
import org.web3j.protocol.Web3j; | |
import org.web3j.protocol.Web3jService; | |
import org.web3j.protocol.admin.Admin; | |
import org.web3j.protocol.admin.methods.response.NewAccountIdentifier; | |
import org.web3j.protocol.core.DefaultBlockParameterName; | |
import org.web3j.protocol.core.DefaultBlockParameterNumber; | |
import org.web3j.protocol.core.RemoteCall; | |
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt; | |
import org.web3j.protocol.core.methods.response.Log; | |
import org.web3j.protocol.core.methods.response.Transaction; | |
import org.web3j.protocol.core.methods.response.TransactionReceipt; | |
import org.web3j.protocol.ipc.WindowsIpcService; | |
import org.web3j.tx.gas.ContractGasProvider; | |
import org.web3j.tx.gas.DefaultGasProvider; | |
import org.web3j.utils.Convert; | |
import org.web3j.utils.Convert.Unit; | |
import org.web3j.utils.Numeric; | |
/** | |
* @author ADG5COB | |
*/ | |
public class Web3jHandler implements IBlockchainEnvironmentChangedListener { | |
private final Account personalAccount = new Account(); | |
private String eventInfo = ""; | |
private static Web3jHandler web3j; | |
private final List<String> accountPassword = new ArrayList<>(); | |
/** | |
* Environment based contract and class instance maps. Currently there are 2 types 1. EnvironmentType.GETH_CLIENT 2. | |
* EnvironmentType.EMBEDDED_EVM | |
*/ | |
private final Map<String, Map<String, Class<?>>> environmentBasedContractMap = new HashMap<>(); | |
private final Map<String, Map<String, Object>> environmentBasedClassInstanceMap = new HashMap<>(); | |
private EmbeddedEVMObject embeddedEVM = null; | |
private String selectedEnvironment = ""; | |
private Web3jHandler() {} | |
/** | |
* @return - | |
*/ | |
public static Web3jHandler getInstance() { | |
if (web3j == null) { | |
web3j = new Web3jHandler(); | |
web3j.initializeEVMConfigurationMap(); | |
BlockchainEnvironmentChangedTrigger.getInstance().addBlockchainEnvironmentChangedListener(web3j); | |
} | |
if (web3j.accountPassword.isEmpty()) { | |
web3j.accountPassword.add("123"); | |
web3j.accountPassword.add("123"); | |
web3j.accountPassword.add("123"); | |
} | |
return web3j; | |
} | |
/** | |
* Before calling this method make sure Geth server is started | |
* | |
* @throws IOException - | |
* @throws InterruptedException - | |
* @throws ExecutionException - | |
*/ | |
public String createInitialAccounts() throws IOException, InterruptedException, ExecutionException { | |
while (true) { | |
if (CoreCommandExecutor.getInstance().isGethStarted()) { | |
break; | |
} | |
if (!CoreCommandExecutor.getInstance().getGethServerStartError().replace("Geth", "").isEmpty()) { | |
return CoreCommandExecutor.getInstance().getGethServerStartError().replace("Geth", ""); | |
} | |
Thread.sleep(500); | |
} | |
List<String> result = getAdminInstance().personalListAccounts().send().getResult(); | |
if (result.isEmpty()) { | |
// Then no accounts in the data directory create new accounts | |
NewAccountIdentifier account1 = getAdminInstance().personalNewAccount("123").send(); | |
NewAccountIdentifier account2 = getAdminInstance().personalNewAccount("123").send(); | |
NewAccountIdentifier account3 = getAdminInstance().personalNewAccount("123").send(); | |
Map<String, String> accountsMap = new LinkedHashMap<>(); | |
accountsMap.put(account1.getAccountId(), "123"); | |
accountsMap.put(account2.getAccountId(), "123"); | |
accountsMap.put(account3.getAccountId(), "123"); | |
this.personalAccount.setDataDir(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
CoreCommandExecutor.getInstance().getDataDir()); | |
this.personalAccount.setAccounts(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
accountsMap); | |
// Terminate geth server | |
CoreCommandExecutor.getInstance().terminateGethServer(); | |
while (true) { | |
if (!CoreCommandExecutor.getInstance().isGethStarted()) { | |
break; | |
} | |
Thread.sleep(500); | |
} | |
// Start new geth server with new accounts created above | |
CoreCommandExecutor.getInstance().startGethServer(CoreCommandExecutor.getInstance().getDataDir(), | |
createGethInitFile(CoreCommandExecutor.getInstance().getDataDir(), true, | |
SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString()), | |
CoreCommandExecutor.getInstance().getGethOptions()); | |
while (true) { | |
if (CoreCommandExecutor.getInstance().isGethStarted()) { | |
break; | |
} | |
if (!CoreCommandExecutor.getInstance().getGethServerStartError().replace("Geth", "").isEmpty()) { | |
return CoreCommandExecutor.getInstance().getGethServerStartError().replace("Geth", ""); | |
} | |
Thread.sleep(500); | |
} | |
Map<String, String> balanceMap = new LinkedHashMap<>(); | |
balanceMap.put(account1.getAccountId(), convertToEther(getAdminInstance() | |
.ethGetBalance(account1.getAccountId(), new DefaultBlockParameterNumber(0)).send().getBalance().toString())); | |
balanceMap.put(account2.getAccountId(), convertToEther(getAdminInstance() | |
.ethGetBalance(account2.getAccountId(), new DefaultBlockParameterNumber(0)).send().getBalance().toString())); | |
balanceMap.put(account3.getAccountId(), convertToEther(getAdminInstance() | |
.ethGetBalance(account3.getAccountId(), new DefaultBlockParameterNumber(0)).send().getBalance().toString())); | |
this.personalAccount.setAccountBalance(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
balanceMap); | |
// Unlock newly created accounts | |
getAdminInstance().personalUnlockAccount(account1.getAccountId(), "123"); | |
getAdminInstance().personalUnlockAccount(account2.getAccountId(), "123"); | |
getAdminInstance().personalUnlockAccount(account3.getAccountId(), "123"); | |
} | |
else { | |
// Accounts are already present load them | |
Map<String, String> accountsMap = new LinkedHashMap<>(); | |
Map<String, String> balanceMap = new LinkedHashMap<>(); | |
String[] account = new String[3]; | |
for (int i = 0; i < result.size(); i++) { | |
accountsMap.put(result.get(i), this.accountPassword.get(i)); | |
account[i] = result.get(i); | |
} | |
balanceMap.put(account[0], convertToEther(getAdminInstance() | |
.ethGetBalance(account[0], DefaultBlockParameterName.LATEST).sendAsync().get().getBalance().toString())); | |
balanceMap.put(account[1], convertToEther(getAdminInstance() | |
.ethGetBalance(account[1], DefaultBlockParameterName.LATEST).sendAsync().get().getBalance().toString())); | |
balanceMap.put(account[2], convertToEther(getAdminInstance() | |
.ethGetBalance(account[2], DefaultBlockParameterName.LATEST).sendAsync().get().getBalance().toString())); | |
this.personalAccount.setAccountBalance(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
balanceMap); | |
this.personalAccount.setDataDir(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
CoreCommandExecutor.getInstance().getDataDir()); | |
this.personalAccount.setAccounts(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString(), | |
accountsMap); | |
getAdminInstance().personalUnlockAccount(account[0], "123"); | |
getAdminInstance().personalUnlockAccount(account[1], "123"); | |
getAdminInstance().personalUnlockAccount(account[2], "123"); | |
} | |
// add listener for tranactions | |
addListenerForTransactions(); | |
return ""; | |
} | |
/** | |
* @param projectName - | |
* @param scName - | |
* @param solOutputDir - | |
* @param solidityFileAbsPath - | |
* @param values - | |
* @param accountDetails - | |
* @return - | |
* @throws Exception - | |
*/ | |
public String deploySmartContract(final String projectName, final String scName, final String solOutputDir, | |
final String solidityFileAbsPath, final String values, final String accountDetails) | |
throws Exception { | |
try { | |
if ((getSelectedEnvironment().equals(SecoBlocksPreferenceConstants.EnvironmentType.GETH_CLIENT.toString())) && | |
(!CoreCommandExecutor.getInstance().isGethStarted())) { | |
return "Geth Server is not started"; | |
} | |
Class<?> contract; | |
Object classInstance; | |
Web3j web3jLocal = getWeb3jInstance(); | |
Map<String, String> accounts = this.personalAccount.getAccounts(getSelectedEnvironment()); | |
Iterator<String> iterator = accounts.keySet().iterator(); | |
String accountInfo = null; | |
while (iterator.hasNext()) { | |
String account = iterator.next(); | |
if (account.equals(accountDetails)) { | |
accountInfo = account; | |
break; | |
} | |
} | |
if (accountInfo != null) { | |
String[] credentials = getWalletFile(accountInfo.replace("0x", ""), | |
this.personalAccount.getDataDir(getSelectedEnvironment()) + File.separator + "keystore").split("~"); | |
Credentials creds = WalletUtils.loadCredentials(credentials[1], credentials[0]); | |
ContractGasProvider contractGasProvider = new DefaultGasProvider(); | |
DynamicClassLoader dcl = new DynamicClassLoader(this.getClass().getClassLoader(), projectName, solOutputDir); | |
contract = dcl.loadClass("com.bosch." + scName); | |
SolidityABI[] solABI = CoreCommandExecutor.getInstance().getSolABIMap(solidityFileAbsPath); | |
Class<?>[] cls = getConstructorArgTypes(solABI); | |
Object[] objs = getConstructorArgsAsObject(solABI, web3jLocal, creds, contractGasProvider, values); | |
RemoteCall<?> dep = (RemoteCall<?>) contract.getMethod("deploy", cls).invoke(contract, objs); | |
if (!SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString().equals(getSelectedEnvironment())) { | |
CoreCommandExecutor.getInstance().mine(true); | |
} | |
classInstance = dep.send(); | |
EthereumProject ethProject = ProjectCreator.getInstance().getEthProject(projectName); | |
ethProject.createDeploymentModel(contract.getMethod("getContractAddress").invoke(classInstance).toString(), | |
ethProject.getProject() | |
.getFile(solidityFileAbsPath.replace(ethProject.getProject().getLocation().toOSString(), "")), | |
accountInfo, getSelectedEnvironment()); | |
Map<String, Class<?>> tempContractMap = | |
this.environmentBasedContractMap.computeIfAbsent(getSelectedEnvironment(), m -> new HashMap<>()); | |
tempContractMap.put(solidityFileAbsPath, contract); | |
Map<String, Object> tempClassInstanceMap = | |
this.environmentBasedClassInstanceMap.computeIfAbsent(getSelectedEnvironment(), m -> new HashMap<>()); | |
tempClassInstanceMap.put(solidityFileAbsPath, classInstance); | |
} | |
} | |
finally { | |
updateAccBalance(); | |
if (!SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString().equals(getSelectedEnvironment())) { | |
CoreCommandExecutor.getInstance().mine(false); | |
} | |
} | |
return ""; | |
} | |
/** | |
* @throws IOException | |
* @throws ExecutionException | |
* @throws InterruptedException | |
*/ | |
private void updateAccBalance() throws InterruptedException, ExecutionException { | |
Map<String, String> accountBalance = this.personalAccount.getAccountBalance(getSelectedEnvironment()); | |
Iterator<String> iterator = accountBalance.keySet().iterator(); | |
while (iterator.hasNext()) { | |
String accountID = iterator.next(); | |
if (SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString().equals(getSelectedEnvironment())) { | |
accountBalance.put(accountID, convertToEther(getWeb3jInstance() | |
.ethGetBalance(accountID, DefaultBlockParameterName.LATEST).sendAsync().get().getBalance().toString())); | |
} | |
else { | |
accountBalance.put(accountID, convertToEther(getAdminInstance() | |
.ethGetBalance(accountID, DefaultBlockParameterName.LATEST).sendAsync().get().getBalance().toString())); | |
} | |
} | |
} | |
private Class<?>[] getConstructorArgTypes(final SolidityABI[] solABI) { | |
Class<?>[] classType = null; | |
for (SolidityABI sAbi : solABI) { | |
if (sAbi.getType().equalsIgnoreCase("constructor")) { | |
int inpsLength = sAbi.getInputs().length; | |
classType = new Class<?>[inpsLength + 3]; | |
classType[0] = Web3j.class; | |
classType[1] = Credentials.class; | |
classType[2] = ContractGasProvider.class; | |
for (int i = 3; i < (inpsLength + 3); i++) { | |
classType[i] = sAbi.getInputs()[(i - 3)].getJavaType(); | |
} | |
} | |
} | |
if (classType == null) {// Contracts w/o constructor | |
classType = new Class<?>[3]; | |
classType[0] = Web3j.class; | |
classType[1] = Credentials.class; | |
classType[2] = ContractGasProvider.class; | |
} | |
return classType; | |
} | |
private Object[] getConstructorArgsAsObject(final SolidityABI[] solABI, final Web3j web3jLocal, | |
final Credentials creds, final ContractGasProvider contractGasProvider, final String values) { | |
Object[] objs = null; | |
String[] inputVals = values.split(";"); | |
for (SolidityABI sAbi : solABI) { | |
if (sAbi.getType().equalsIgnoreCase("constructor")) { | |
int inpsLength = sAbi.getInputs().length; | |
objs = new Object[inpsLength + 3]; | |
objs[0] = web3jLocal; | |
objs[1] = creds; | |
objs[2] = contractGasProvider; | |
for (int i = 3; i < (inpsLength + 3); i++) { | |
objs[i] = getVariable(inputVals[(i - 3)], sAbi.getInputs()[(i - 3)].getJavaType(), | |
sAbi.getInputs()[(i - 3)].getCastType(), sAbi.getInputs()[(i - 3)].getIsArray()); | |
} | |
} | |
} | |
if (objs == null) {// Contracts w/o constructor | |
objs = new Object[3]; | |
objs[0] = web3jLocal; | |
objs[1] = creds; | |
objs[2] = contractGasProvider; | |
} | |
return objs; | |
} | |
private Object getVariable(final String value, final Class<?> type, final Class<?> castType, final boolean isArray) { | |
Object castValue = null; | |
castValue = SolidityDynamicValueCasterHandler.getValue(value, (isArray ? castType : type), isArray); | |
return castValue; | |
} | |
/** | |
* This method is used to invoke the smart contract function... | |
* | |
* @param projectName - | |
* @param funcName | |
* @param scAbsPath | |
* @param values - The input params for the function to be invoked, should be a comma separated value. | |
* @param accountDetails - | |
* @return | |
* @throws Exception | |
*/ | |
public String invokeSCFunction(final String projectName, final String funcName, final String scAbsPath, | |
final String values, final String accountDetails) | |
throws Exception { | |
String response = ""; | |
Object responseObject = null; | |
boolean returnPresent = false; | |
Map<String, Class<?>> tempContractMap = | |
this.environmentBasedContractMap.computeIfAbsent(getSelectedEnvironment(), m -> new HashMap<>()); | |
Class<?> contract = tempContractMap.get(scAbsPath); | |
Map<String, Object> tempClassInstanceMap = | |
this.environmentBasedClassInstanceMap.computeIfAbsent(getSelectedEnvironment(), m -> new HashMap<>()); | |
Object classInstance = tempClassInstanceMap.get(scAbsPath); | |
try { | |
String[] funcDetails = funcName.split("-"); | |
String argList = ""; | |
if (funcDetails.length > 1) { | |
argList = funcDetails[1]; | |
} | |
SolidityABI[] solABI = CoreCommandExecutor.getInstance().getSolABIMap(scAbsPath); | |
String[] inputVals = new String[0]; | |
if (!values.isEmpty()) { | |
inputVals = values.split(";"); | |
} | |
SolidityABI masterSolABI = getSolABI(solABI, funcDetails[0], argList, inputVals); | |
if (masterSolABI != null) { | |
Class<?>[] funcArgs = getFuncArgs(masterSolABI); | |
EthereumProject ethProject = ProjectCreator.getInstance().getEthProject(projectName); | |
// Re-Load contract with new credentials - if different account is chosen | |
String lastUsedAccount = ethProject.getLastUsedAccount( | |
ethProject.getProject().getFile(scAbsPath.replace(ethProject.getProject().getLocation().toOSString(), "")), | |
getSelectedEnvironment()); | |
if (!accountDetails.equals(lastUsedAccount)) { | |
String[] credentials = getWalletFile(accountDetails.replace("0x", ""), | |
this.personalAccount.getDataDir(getSelectedEnvironment()) + File.separator + "keystore").split("~"); | |
Web3j web3jInstance = getWeb3jInstance(); | |
Credentials creds = WalletUtils.loadCredentials(credentials[1], credentials[0]); | |
ContractGasProvider contractGasProvider = new DefaultGasProvider(); | |
classInstance = | |
contract.getMethod("load", String.class, Web3j.class, Credentials.class, ContractGasProvider.class) | |
.invoke(classInstance, | |
ethProject.getContractAddress( | |
ethProject.getProject() | |
.getFile(scAbsPath.replace(ethProject.getProject().getLocation().toOSString(), "")), | |
getSelectedEnvironment()), | |
web3jInstance, creds, contractGasProvider); | |
ethProject.updateLastUsedAccount( | |
ethProject.getProject() | |
.getFile(scAbsPath.replace(ethProject.getProject().getLocation().toOSString(), "")), | |
accountDetails, getSelectedEnvironment()); | |
/** | |
* This is required because when account is changed(Acc1 to Acc2) the classInstance will be loaded here with | |
* the new account(Acc2) and hence the tempclassinstancemap should also be updated with the newly loaded | |
* classinstance | |
*/ | |
tempClassInstanceMap.put(scAbsPath, classInstance); | |
} | |
RemoteCall<?> dep = (RemoteCall<?>) contract.getMethod(funcDetails[0], funcArgs).invoke(classInstance, | |
getFuncValues(values, masterSolABI.getInputs())); | |
returnPresent = doesFunctionReturnSomething(masterSolABI); | |
if (!SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString().equals(getSelectedEnvironment())) { | |
CoreCommandExecutor.getInstance().mine(true); | |
} | |
// response = dep.send().toString(); | |
responseObject = dep.send(); | |
// responseObject = SolidityDynamicValueCasterHandler.getValue(projectName, valueToCast, actualType, isArray) | |
if (returnPresent && masterSolABI.getOutputs()[0].getJavaType().equals(List.class) && | |
masterSolABI.getOutputs()[0].getCastType().getSimpleName().contains("byte")) { | |
List<byte[]> bytes = (List<byte[]>) responseObject; | |
for (byte[] b : bytes) { | |
response += Numeric.toHexString(b) + ";"; | |
} | |
} | |
else if (returnPresent && masterSolABI.getOutputs()[0].getJavaType().getSimpleName().contains("byte")) { | |
response = Numeric.toHexString((byte[]) responseObject); | |
} | |
else { | |
response = responseObject.toString(); | |
} | |
} | |
else { | |
returnPresent = true; | |
response = "Something is not rite. Please make sure you pass the correct arguments"; | |
} | |
this.eventInfo = ""; | |
TransactionReceipt transactionReceipt = (TransactionReceipt) responseObject; | |
ArrayList<Event> events = new ArrayList<Event>(); | |
java.lang.reflect.Field[] s = classInstance.getClass().getFields(); | |
for (Field field : s) { | |
if (field.getType() == Event.class) { | |
events.add((Event) field.get(this)); | |
} | |
} | |
for (Event event : events) { | |
if (transactionReceipt != null) { | |
List<List<org.web3j.abi.datatypes.Type>> eventValues = new ArrayList<>(); | |
List<org.web3j.abi.datatypes.Type> nonIndexed = null, Indexed = null; | |
List<Log> logs = transactionReceipt.getLogs(); | |
for (Log log : logs) { | |
try { | |
nonIndexed = FunctionReturnDecoder.decode(log.getData(), event.getNonIndexedParameters()); | |
Indexed = FunctionReturnDecoder.decode(log.getData(), event.getIndexedParameters()); | |
} | |
catch (Exception e) { | |
continue; | |
} | |
} | |
if (!nonIndexed.isEmpty()) { | |
eventValues.add(nonIndexed); | |
} | |
if (!Indexed.isEmpty()) { | |
eventValues.add(Indexed); | |
} | |
for (List<org.web3j.abi.datatypes.Type> listType : eventValues) { | |
for (org.web3j.abi.datatypes.Type type : listType) { | |
this.eventInfo = this.eventInfo + event.getName() + "\t" + "Type " + type.getTypeAsString() + "\t" + | |
" Value " + type.getValue() + "\n"; | |
} | |
} | |
} | |
} | |
} | |
finally { | |
updateAccBalance(); | |
if (!SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString().equals(getSelectedEnvironment())) { | |
CoreCommandExecutor.getInstance().mine(false); | |
} | |
} | |
return (returnPresent) ? response : "DONE"; | |
} | |
/** | |
* @return Event Information | |
*/ | |
public String getEvents() { | |
if (this.eventInfo != null) { | |
return this.eventInfo; | |
} | |
return "No Event Information"; | |
} | |
/** | |
* As of now restricted to String check.... | |
* | |
* @param masterSolABI | |
* @param funcName | |
* @return | |
*/ | |
private boolean doesFunctionReturnSomething(final SolidityABI masterSolABI) { | |
if (masterSolABI.getOutputs().length > 0) { | |
for (IOABI iob : masterSolABI.getOutputs()) { | |
if ((iob.getJavaType() != null) && !iob.getJavaType().getSimpleName().isEmpty()) { | |
return true; | |
} | |
} | |
return false; | |
} | |
return false; | |
} | |
private Class<?>[] getFuncArgs(final SolidityABI solABI) { | |
Class<?>[] argTypes = null; | |
IOABI[] inputs = solABI.getInputs(); | |
argTypes = new Class[inputs.length]; | |
for (int i = 0; i < inputs.length; i++) { | |
argTypes[i] = inputs[i].getJavaType(); | |
} | |
return argTypes; | |
} | |
private SolidityABI getSolABI(final SolidityABI solABI[], final String funcName, final String argsList, | |
final String[] inputVals) { | |
List<String> inputArgs = new ArrayList<>(); | |
if (argsList.contains(";")) { | |
inputArgs = Arrays.asList(argsList.split(";")); | |
} | |
else if (!argsList.isEmpty()) { | |
inputArgs.add(argsList); | |
} | |
for (SolidityABI sAbi : solABI) { | |
if (sAbi.getName().equalsIgnoreCase(funcName) && (sAbi.getInputs().length == inputVals.length)) { | |
List<String> argVals = new ArrayList<>(); | |
for (IOABI input : sAbi.getInputs()) { | |
argVals.add(input.getJavaType().getSimpleName()); | |
} | |
if (inputArgs.equals(argVals)) { | |
return sAbi; | |
} | |
} | |
} | |
return null; | |
} | |
private Object[] getFuncValues(final String values, final IOABI[] ioabi) { | |
if (values.isEmpty()) { | |
return null; | |
} | |
String[] inputVals = values.split(";"); | |
Object[] vals = new Object[inputVals.length]; | |
for (int i = 0; i < inputVals.length; i++) { | |
vals[i] = getVariable(inputVals[i], ioabi[i].getJavaType(), ioabi[i].getCastType(), ioabi[i].getIsArray()); | |
} | |
return vals; | |
} | |
private String convertToEther(final String wei) { | |
return Convert.fromWei(wei, Unit.ETHER).toString(); | |
} | |
private String createGethInitFile(final String dataDir, final boolean clearOldData, final String accountType) | |
throws IOException, InterruptedException { | |
if (clearOldData) { | |
clearOldDataDir(dataDir); | |
} | |
File gethInitFile = new File(dataDir + File.separator + "genesis.json"); | |
gethInitFile.createNewFile();// NOSONAR | |
List<String> accounts = new ArrayList<>(this.personalAccount.getAccounts(accountType).keySet()); | |
try ( | |
BufferedReader br = new BufferedReader( | |
new InputStreamReader(CoreCommandExecutor.class.getResourceAsStream("genesis2.template"))); | |
BufferedWriter bw = new BufferedWriter(new FileWriter(gethInitFile));) { | |
String genesisContent = ""; | |
while ((genesisContent = br.readLine()) != null) { | |
if (genesisContent.contains("acc")) { | |
int index = Integer | |
.parseInt(genesisContent.substring(genesisContent.indexOf("acc") + 3, genesisContent.indexOf("acc") + 4)); | |
genesisContent = genesisContent.replace("acc" + index, accounts.get(index)); | |
} | |
bw.append(genesisContent); | |
bw.append(System.lineSeparator()); | |
} | |
} | |
return gethInitFile.getAbsolutePath(); | |
} | |
private static void clearOldDataDir(final String dataDir) throws IOException, InterruptedException { | |
int maxTries = 3; | |
while (maxTries >= 1) { | |
try { | |
FileUtils.deleteDirectory(new File(dataDir + File.separator + "geth")); | |
break; | |
} | |
catch (IOException e) { | |
if (maxTries > 1) { | |
maxTries--; | |
Thread.sleep(500); | |
} | |
else { | |
throw e; | |
} | |
} | |
} | |
new File(dataDir + File.separator + "genesis.json").delete();// NOSONAR | |
} | |
/** | |
* Returns account wallet file along with its password walletFile~password | |
* | |
* @param accId | |
* @param keyStore | |
* @return | |
*/ | |
private String getWalletFile(final String accId, final String keyStore) { | |
File directory = new File(keyStore); | |
File[] listFiles = directory.listFiles(); | |
for (File f : listFiles) { | |
if (f.getName().contains(accId)) { | |
return f.getAbsolutePath() + "~" + this.personalAccount.getAccounts(getSelectedEnvironment()).get("0x" + accId); | |
} | |
} | |
return ""; | |
} | |
/** | |
* @return - The personal accounts | |
*/ | |
public Account getAccount() { | |
return this.personalAccount; | |
} | |
/** | |
* As of now only single account is supported in embeddedEVM | |
*/ | |
private void initializeEVMConfigurationMap() { | |
try { | |
String workspaceLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString(); | |
File evmWalletLocation = | |
new File(workspaceLocation + File.separator + "evmWalletLocation" + File.separator + "keystore"); | |
if (evmWalletLocation.exists()) { | |
Files.walkFileTree(Paths.get(evmWalletLocation.getAbsolutePath()), new SimpleFileVisitor<Path>() { | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { | |
Files.delete(file); | |
return FileVisitResult.CONTINUE; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { | |
Files.delete(dir); | |
return FileVisitResult.CONTINUE; | |
} | |
}); | |
} | |
evmWalletLocation.mkdirs(); | |
// WalletFiles creation | |
String walletFile1 = WalletUtils.generateFullNewWalletFile("123", evmWalletLocation); | |
String walletFile2 = WalletUtils.generateFullNewWalletFile("123", evmWalletLocation); | |
String walletFile3 = WalletUtils.generateFullNewWalletFile("123", evmWalletLocation); | |
// Credentials object | |
Credentials cred1 = WalletUtils.loadCredentials("123", | |
new File(evmWalletLocation.getAbsolutePath() + File.separator + walletFile1)); | |
Credentials cred2 = WalletUtils.loadCredentials("123", | |
new File(evmWalletLocation.getAbsolutePath() + File.separator + walletFile2)); | |
Credentials cred3 = WalletUtils.loadCredentials("123", | |
new File(evmWalletLocation.getAbsolutePath() + File.separator + walletFile3)); | |
// Accounts updation | |
Map<String, String> accountsMap = new LinkedHashMap<>(); | |
accountsMap.put(cred1.getAddress(), "123"); | |
accountsMap.put(cred2.getAddress(), "123"); | |
accountsMap.put(cred3.getAddress(), "123"); | |
this.personalAccount.setAccounts(SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString(), | |
accountsMap); | |
Map<String, String> accountBalanceMap = new LinkedHashMap<>(); | |
accountBalanceMap.put(cred1.getAddress(), "100"); | |
accountBalanceMap.put(cred2.getAddress(), "100"); | |
accountBalanceMap.put(cred3.getAddress(), "100"); | |
this.personalAccount.setAccountBalance(SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString(), | |
accountBalanceMap); | |
this.personalAccount.setDataDir(SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString(), | |
evmWalletLocation.getParent()); | |
// genesis file creation | |
String genesisFile = createGethInitFile(evmWalletLocation.getParent(), false, | |
SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString()); | |
// Config object | |
Configuration config1 = | |
new Configuration(new Address(cred1.getAddress()), 100, new File(genesisFile).toURI().toURL()); | |
Web3jService embeddedWeb3jService = new EmbeddedWeb3jService(config1); | |
Web3j web1 = Web3j.build(embeddedWeb3jService); | |
Admin admin1 = Admin.build(new EmbeddedWeb3jService(config1)); | |
this.embeddedEVM = new EmbeddedEVMObject(config1, web1, admin1); | |
/** | |
* Transaction flowable is not supported by embedded EVM hence it is commented for now | |
*/ | |
// addListenerForTransactions(); | |
} | |
catch (IOException | NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | |
| CipherException | InterruptedException e) { | |
BlockchainCore.getInstance().logException(BlockchainCore.PLUGIN_ID, "Exception in evm config initialization", e); | |
} | |
} | |
/** | |
* @return - The environment web3j instance, if selected environment is geth then geth server should be started else | |
* null will be returned | |
*/ | |
public Web3j getWeb3jInstance() { | |
if (getSelectedEnvironment().equals(SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString())) { | |
return this.embeddedEVM.getWeb3j(); | |
} | |
if (CoreCommandExecutor.getInstance().isGethStarted()) { | |
return Web3j.build(new WindowsIpcService(CoreCommandExecutor.getInstance().getIPCPath())); | |
} | |
return null; | |
} | |
/** | |
* @return - The environment Admin instance, if selected environment is geth then geth server should be started else | |
* null will be returned | |
*/ | |
public Admin getAdminInstance() { | |
if (getSelectedEnvironment().equals(SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString())) { | |
return this.embeddedEVM.getAdmin(); | |
} | |
if (CoreCommandExecutor.getInstance().isGethStarted()) { | |
return Admin.build(new WindowsIpcService(CoreCommandExecutor.getInstance().getIPCPath())); | |
} | |
return null; | |
} | |
/** | |
* @return - | |
*/ | |
public BigInteger getGasPrice() { | |
BigInteger gasPrice = null; | |
try { | |
gasPrice = getAdminInstance().ethGasPrice().send().getGasPrice(); | |
} | |
catch (IOException e) { | |
BlockchainCore.getInstance().logException("", e.getMessage(), e); | |
} | |
return gasPrice; | |
} | |
private void addListenerForTransactions() { | |
getWeb3jInstance().transactionFlowable().subscribe(tx -> { | |
addToGlobalTransactionMap(tx); | |
}); | |
} | |
/** | |
* Get transaction receipt for already mined transaction | |
* | |
* @param transactionHash - The transaction hash | |
* @return - The transaction receipt for the requested transaction | |
* @throws IOException - Is thrown if socket connection get terminated | |
*/ | |
public String getTransactionReceiptAsString(final String transactionHash) throws IOException { | |
EthGetTransactionReceipt transactionReceipt = getWeb3jInstance().ethGetTransactionReceipt(transactionHash).send(); | |
return getTransactionReceiptMessage(transactionReceipt.getResult()); | |
} | |
private String getTransactionReceiptMessage(final TransactionReceipt transactionReceipt) { | |
return "Transaction Hash :" + transactionReceipt.getTransactionHash() + System.lineSeparator() + | |
"Transaction Index :" + transactionReceipt.getTransactionIndex() + System.lineSeparator() + "Block Hash :" + | |
transactionReceipt.getBlockHash() + System.lineSeparator() + "Block Number :" + | |
transactionReceipt.getBlockNumber() + System.lineSeparator() + "Cumulative Gas Used :" + | |
transactionReceipt.getCumulativeGasUsed() + System.lineSeparator() + "Gas Used :" + | |
transactionReceipt.getGasUsed() + System.lineSeparator() + "Contract Address :" + | |
transactionReceipt.getContractAddress() + System.lineSeparator() + "Root :" + transactionReceipt.getRoot() + | |
System.lineSeparator() + "Status :" + transactionReceipt.getStatus() + System.lineSeparator() + "From :" + | |
transactionReceipt.getFrom() + System.lineSeparator() + "To :" + transactionReceipt.getTo() + | |
System.lineSeparator() + "Logs :" + transactionReceipt.getLogs().toString() + System.lineSeparator() + | |
"Logs Bloom :" + transactionReceipt.getLogsBloom() + System.lineSeparator(); | |
} | |
/** | |
* @param tx | |
*/ | |
private void addToGlobalTransactionMap(final Transaction tx) { | |
// create an intermediate transaction model with the actual transaction data | |
TransactionModel transactionModel = new TransactionModel(); | |
transactionModel.setValues(tx); | |
// refresh the transaction view with data | |
BlockchainViewsRegistry.getListOFViews().stream().forEach(view -> view.updateView(transactionModel)); | |
} | |
String getSelectedEnvironment() { | |
if (!this.selectedEnvironment.isEmpty()) { | |
return this.selectedEnvironment; | |
} | |
return this.selectedEnvironment = InstanceScope.INSTANCE.getNode(SecoBlocksPreferenceConstants.SECOBLOCKS_PREF_NODE) | |
.get(SecoBlocksPreferenceConstants.ENVIRONMENT_PREF_KEY, | |
SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString()); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void blockchainEnvironmentChanged(final IBlockchainEnvironmentChangedEvent event) { | |
this.selectedEnvironment = event.getActiveEvironment(); | |
} | |
} |