All Downloads are FREE. Search and download functionalities are using the official Maven repository.

convex.cli.Main Maven / Gradle / Ivy

package convex.cli;

import java.io.File;
import java.lang.NumberFormatException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


import convex.api.Convex;
import convex.cli.peer.SessionItem;
import convex.cli.output.Output;
import convex.core.crypto.AKeyPair;
import convex.core.crypto.PFXTools;
import convex.core.data.AccountKey;
import convex.core.data.Address;
import convex.core.init.Init;


import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.PropertiesDefaultProvider;
import picocli.CommandLine.ScopeType;

/**
* Convex CLI implementation
*/
@Command(name="convex",
	subcommands = {
		Account.class,
		Key.class,
		Local.class,
		Peer.class,
		Query.class,
		Status.class,
		Transaction.class,
		CommandLine.HelpCommand.class
	},
	mixinStandardHelpOptions=true,
	usageHelpAutoWidth=true,
	sortOptions = false,
	// headerHeading = "Usage:",
	// synopsisHeading = "%n",
	descriptionHeading = "%nDescription:%n%n",
	parameterListHeading = "%nParameters:%n",
	optionListHeading = "%nOptions:%n",
	commandListHeading = "%nCommands:%n",
	description="Convex Command Line Interface")

public class Main implements Runnable {

	private static Logger log = LoggerFactory.getLogger(Main.class);


	private static CommandLine commandLine;
	public Output output;

	@Option(names={ "-c", "--config"},
		scope = ScopeType.INHERIT,
		description="Use the specified config file.%n All parameters to this app can be set by removing the leading '--', and adding"
			+ " a leading 'convex.'.%n So to set the keystore filename you can write 'convex.keystore=my_keystore_filename.dat'%n"
			+ "To set a sub command such as `./convex peer start index=4` index parameter you need to write 'convex.peer.start.index=4'")
	private String configFilename;

    @Option(names={"-e", "--etch"},
		scope = ScopeType.INHERIT,
		description="Convex state storage filename. The default is to use a temporary storage filename.")
	private String etchStoreFilename;

	@Option(names={"-k", "--keystore"},
		defaultValue=Constants.KEYSTORE_FILENAME,
		scope = ScopeType.INHERIT,
		description="keystore filename. Default: ${DEFAULT-VALUE}")
	private String keyStoreFilename;

	@Option(names={"-p", "--password"},
		scope = ScopeType.INHERIT,
		//defaultValue="",
		description="Password to read/write to the Keystore")
	private String password;

	@Option(names={"-s", "--session"},
	defaultValue=Constants.SESSION_FILENAME,
    scope = ScopeType.INHERIT,
	description="Session filename. Defaults ${DEFAULT-VALUE}")
	private String sessionFilename;

    @Option(names={ "-v", "--verbose"},
		scope = ScopeType.INHERIT,
		description="Show more verbose log information. You can increase verbosity by using multiple -v or -vvv")
	private boolean[] verbose = new boolean[0];


	public Main() {
		output = new Output();
	}

	@Override
	public void run() {
		// no command provided - so show help
		CommandLine.usage(new Main(), System.out);
	}

	public static void main(String[] args) {
		Main mainApp = new Main();
		int result = mainApp.execute(args);
		System.exit(result);
	}

	public int execute(String[] args) {
		commandLine = new CommandLine(this)
		.setUsageHelpLongOptionsMaxWidth(40)
		.setUsageHelpWidth(40 * 4);

		// do  a pre-parse to get the config filename. We need to load
		// in the defaults before running the full execute
		try {
			commandLine.parseArgs(args);
			loadConfig();
		} catch (Throwable t) {
			System.err.println("unable to parse arguments " + t);
		}

		ch.qos.logback.classic.Logger parentLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

		Level[] verboseLevels = {Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE, Level.ALL};

		parentLogger.setLevel(Level.WARN);
		if (verbose.length > 0 && verbose.length <= verboseLevels.length) {
			parentLogger.setLevel(verboseLevels[verbose.length]);
			log.info("set level to {}", parentLogger.getLevel());
		}

		int result = 0;
		try {
			result = commandLine.execute(args);
			output.writeToStream(commandLine.getOut());

		} catch (Throwable t) {
			log.error("Error executing command line: {}",t.getMessage());
			return 2;
		}
		return result;
	}

	protected void loadConfig() {
		if (configFilename != null && !configFilename.isEmpty()) {
			String filename = Helpers.expandTilde(configFilename);
			File configFile = new File(filename);
			if (configFile.exists()) {
				PropertiesDefaultProvider defaultProvider = new PropertiesDefaultProvider(configFile);
				commandLine.setDefaultValueProvider(defaultProvider);
			}
		}
	}

	public String getSessionFilename() {
        if (sessionFilename != null) {
			return Helpers.expandTilde(sessionFilename.strip());
		}
		return null;
	}

	public String getPassword() {
		return password;
	}

	public String getKeyStoreFilename() {
		if ( keyStoreFilename != null) {
			return Helpers.expandTilde(keyStoreFilename).strip();
		}
		return null;
	}

	public String getEtchStoreFilename() {
		if ( etchStoreFilename != null) {
			return Helpers.expandTilde(etchStoreFilename).strip();
		}
		return null;
	}

	public KeyStore loadKeyStore(boolean isCreate) throws Error {
		KeyStore keyStore = null;
		if (password == null || password.isEmpty()) {
			throw new Error("You need to provide a keystore password");
		}
		File keyFile = new File(getKeyStoreFilename());
		try {
			if (keyFile.exists()) {
				keyStore = PFXTools.loadStore(keyFile, password);
			}
			else {
				if (isCreate) {
					Helpers.createPath(keyFile);
					keyStore = PFXTools.createStore(keyFile, password);
				}
				else {
					throw new Error("Cannot find keystore file "+keyFile.getCanonicalPath());
				}
			}
		} catch(Throwable t) {
			new Error(t);
		}
		return keyStore;
	}

	public AKeyPair loadKeyFromStore(String publicKey, int indexKey) throws Error {

		AKeyPair keyPair = null;

		String publicKeyClean = "";
		if (publicKey != null) {
			publicKeyClean = publicKey.toLowerCase().replaceAll("^0x", "").strip();
		}

		if ( publicKeyClean.isEmpty() && indexKey <= 0) {
			return null;
		}

		String searchText = publicKeyClean;
		if (indexKey > 0) {
			searchText += " " + indexKey;
		}
		if (password == null || password.isEmpty()) {
			throw new Error("You need to provide a keystore password");
		}


		File keyFile = new File(getKeyStoreFilename());
		try {
			if (!keyFile.exists()) {
				throw new Error("Cannot find keystore file "+keyFile.getCanonicalPath());
			}
			KeyStore keyStore = PFXTools.loadStore(keyFile, password);

			int counter = 1;
			Enumeration aliases = keyStore.aliases();

			while (aliases.hasMoreElements()) {
				String alias = aliases.nextElement();
				if (counter == indexKey || alias.indexOf(publicKeyClean) == 0) {
					log.trace("found keypair " + indexKey + " " + counter + " " + alias + " " + publicKeyClean + " " + alias.indexOf(publicKeyClean));
					keyPair = PFXTools.getKeyPair(keyStore, alias, password);
					break;
				}
				counter ++;
			}
		} catch (Throwable t) {
			throw new Error("Cannot load key store "+t);
		}

		if (keyPair==null) {
			throw new Error("Cannot find key in keystore '" + searchText + "'");
		}
		return keyPair;
	}

	public Convex connectToSessionPeer(String hostname, int port, Address address, AKeyPair keyPair) throws Error {
		SessionItem item;
		Convex convex = null;
		try {
			if (port == 0) {
				item = Helpers.getSessionItem(getSessionFilename());
				if (item != null) {
					port = item.getPort();
				}
			}
			if (port == 0) {
				throw new Error("Cannot find a local port or you have not set a valid port number");
			}
			InetSocketAddress host=new InetSocketAddress(hostname.strip(), port);
			convex = Convex.connect(host, address, keyPair);
		} catch (Throwable t) {
			throw new Error("Cannot connect to a local peer " + t);
		}
		return convex;
	}

	public Convex connectAsPeer(int peerIndex) throws Error {
		Convex convex = null;
		try {
			SessionItem item = Helpers.getSessionItem(getSessionFilename(), peerIndex);
			AccountKey peerKey = item.getAccountKey();
			log.debug("peer public key {}", peerKey.toHexString());
			AKeyPair keyPair = loadKeyFromStore(peerKey.toHexString(), 0);
			log.debug("peer key pair {}", keyPair.getAccountKey().toHexString());
			Address address = Init.getGenesisPeerAddress(peerIndex);
			log.debug("peer address {}", address);
			InetSocketAddress host = item.getHostAddress();
			log.debug("connect to peer {}", host);
			convex = Convex.connect(host, address, keyPair);
		} catch (Throwable t) {
			throw new Error("Cannot connect as a peer " + t);
		}
		return convex;
	}

	public List generateKeyPairs(int count) throws Error {
		List keyPairList = new ArrayList<>(count);

		// generate `count` keys
		for (int index = 0; index < count; index ++) {
			AKeyPair keyPair = AKeyPair.generate();
			keyPairList.add(keyPair);
			addKeyPairToStore(keyPair);
		}

		return keyPairList;
	}

	public void addKeyPairToStore(AKeyPair keyPair) {
		// get the password of the key store file
		String password = getPassword();
		if (password == null) {
			throw new Error("You need to provide a keystore password");
		}
		// get the key store file
		File keyFile = new File(getKeyStoreFilename());

		KeyStore keyStore = null;
		try {
			// try to load the keystore file
			if (keyFile.exists()) {
				keyStore = PFXTools.loadStore(keyFile, password);
			} else {
				// create the path to the new key file
				Helpers.createPath(keyFile);
				keyStore = PFXTools.createStore(keyFile, password);
			}
		} catch (Throwable t) {
			throw new Error("Cannot load key store: "+t);
		}
		try {
			// save the key in the keystore
			PFXTools.saveKey(keyStore, keyPair, password);
		} catch (Throwable t) {
			throw new Error("Cannot store the key to the key store "+t);
		}
		// save the keystore file
		try {
			PFXTools.saveStore(keyStore, keyFile, password);
		} catch (Throwable t) {
			throw new Error("Cannot save the key store file "+t);
		}
	}

	int[] getPortList(String ports[], int count) throws NumberFormatException {
		Pattern rangePattern = Pattern.compile(("([0-9]+)\\s*-\\s*([0-9]*)"));
		List portTextList = Helpers.splitArrayParameter(ports);
		List portList = new ArrayList();
		int countLeft = count;
		for (int index = 0; index < portTextList.size() && countLeft > 0; index ++) {
			String item = portTextList.get(index);
			Matcher matcher = rangePattern.matcher(item);
			if (matcher.matches()) {
				int portFrom = Integer.parseInt(matcher.group(1));
				int portTo = portFrom  + count + 1;
				if (!matcher.group(2).isEmpty()) {
					portTo = Integer.parseInt(matcher.group(2));
				}
				for ( int portIndex = portFrom; portIndex <= portTo && countLeft > 0; portIndex ++, --countLeft ) {
					portList.add(portIndex);
				}
			}
			else if (item.strip().length() == 0) {
			}
			else {
				portList.add(Integer.parseInt(item));
				countLeft --;
			}
		}
		return portList.stream().mapToInt(Integer::intValue).toArray();
	}

    void showError(Throwable t) {
		log.error(t.getMessage());
		if (verbose.length > 0) {
			t.printStackTrace();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy