Migrating CLI to Click (#1002)

* Migrating CLI to Click

* Adding more type annotations

* Adding some required to flags

* Some extra small fixes

* Add click dependency to the setup.py file

* Making callable from outside

* Manually applying changes from commit f23c45

* Improving type annotations

* Adding -h as --help option

* Added feedback to add/remove commands for plots

* Properly exiting with error code
This commit is contained in:
Jesús Espino 2021-02-23 11:23:56 +01:00 committed by GitHub
parent dd18113d4d
commit d8410eff83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 660 additions and 915 deletions

View file

@ -23,6 +23,7 @@ dependencies = [
"setproctitle==1.2.2", # Gives the chia processes readable names
"sortedcontainers==2.3.0", # For maintaining sorted mempools
"websockets==8.1.0", # For use in wallet RPC and electron UI
"click==7.1.2", # For the CLI
]
upnp_dependencies = [

View file

@ -1,62 +1,61 @@
import importlib
import pathlib
from argparse import Namespace, ArgumentParser
import asyncio
import click
from pathlib import Path
from src import __version__
from src.util.default_root import DEFAULT_ROOT_PATH
from src.daemon.server import async_run_daemon
from src.cmds.init import init_cmd
from src.cmds.keys import keys_cmd
from src.cmds.plots import plots_cmd
from src.cmds.wallet import wallet_cmd
from src.cmds.configure import configure_cmd
from src.cmds.show import show_cmd
from src.cmds.start import start_cmd
from src.cmds.stop import stop_cmd
from src.cmds.netspace import netspace_cmd
SUBCOMMANDS = [
"init",
"keys",
"show",
"start",
"stop",
"version",
"plots",
"netspace",
"run_daemon",
"wallet",
"configure",
]
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
def create_parser() -> ArgumentParser:
parser: ArgumentParser = ArgumentParser(
description="Manage chia blockchain infrastructure (%s)." % __version__,
@click.group(
help=f"\n Manage chia blockchain infrastructure ({__version__})\n",
epilog="Try 'chia start node', 'chia netspace -d 192', or 'chia show -s'.",
context_settings=CONTEXT_SETTINGS,
)
parser.add_argument(
"--root-path",
help="Config file root (defaults to %s)." % DEFAULT_ROOT_PATH,
type=pathlib.Path,
default=DEFAULT_ROOT_PATH,
)
subparsers = parser.add_subparsers()
# this magic metaprogramming generalizes:
# from src.cmds import version
# new_parser = subparsers.add_parser(version)
# version.version_parser(new_parser)
for subcommand in SUBCOMMANDS:
mod = importlib.import_module("src.cmds.%s" % subcommand)
mod.make_parser(subparsers.add_parser(subcommand)) # type: ignore
parser.set_defaults(function=lambda args, parser: parser.print_help())
return parser
@click.option("--root-path", default=DEFAULT_ROOT_PATH, help="Config file root.", type=click.Path(), show_default=True)
@click.pass_context
def cli(ctx: click.Context, root_path: str) -> None:
ctx.ensure_object(dict)
ctx.obj["root_path"] = Path(root_path)
def chia(args: Namespace, parser: ArgumentParser):
return args.function(args, parser)
@cli.command("version", short_help="show version")
def version_cmd() -> None:
print(__version__)
def main():
parser = create_parser()
args = parser.parse_args()
return chia(args, parser)
@cli.command("run_daemon", short_help="runs chia daemon")
@click.pass_context
def run_daemon_cmd(ctx: click.Context) -> None:
asyncio.get_event_loop().run_until_complete(async_run_daemon(ctx.obj["root_path"]))
cli.add_command(keys_cmd)
cli.add_command(plots_cmd)
cli.add_command(wallet_cmd)
cli.add_command(configure_cmd)
cli.add_command(init_cmd)
cli.add_command(show_cmd)
cli.add_command(start_cmd)
cli.add_command(stop_cmd)
cli.add_command(netspace_cmd)
def main() -> None:
cli() # pylint: disable=no-value-for-parameter
if __name__ == "__main__":

View file

@ -1,76 +1,24 @@
import click
from pathlib import Path
from src.util.config import (
load_config,
save_config,
)
from argparse import ArgumentParser
from typing import Dict
from src.util.default_root import DEFAULT_ROOT_PATH
from src.util.config import str2bool
def make_parser(parser: ArgumentParser):
parser.add_argument(
"--set-node-introducer",
help="Set the introducer for node - IP:Port",
type=str,
nargs="?",
default="",
)
parser.add_argument(
"--set-fullnode-port",
help="Set the port to use for the fullnode",
type=str,
nargs="?",
default="",
)
parser.add_argument(
"--set-log-level",
"--log-level",
"-log-level",
help="Set the instance log level, Can be CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET",
type=str,
nargs="?",
default="",
)
parser.add_argument(
"--enable-upnp",
"--upnp",
"-upnp",
help="Enable or disable uPnP. Can be True or False",
type=str,
nargs="?",
)
parser.set_defaults(function=configure)
def help_message():
print("usage: chia configure -flag")
print(
"""
chia configure [arguments] [inputs]
--set-node-introducer [IP:Port] (Set the introducer for node),
--set-fullnode-port [Port] (Set the full node default port, useful for beta testing),
--set-log-level [LogLevel] (Can be CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET),
--enable-upnp,
--upnp {True,False} (Enable or disable uPnP. Can be True or False)
"""
)
def configure(args, parser):
def configure(root_path: Path, set_node_introducer: str, set_fullnode_port: str, set_log_level: str, enable_upnp: str):
config: Dict = load_config(DEFAULT_ROOT_PATH, "config.yaml")
change_made = False
if args.set_node_introducer:
if set_node_introducer:
try:
if args.set_node_introducer.index(":"):
if set_node_introducer.index(":"):
host, port = (
":".join(args.set_node_introducer.split(":")[:-1]),
args.set_node_introducer.split(":")[-1],
":".join(set_node_introducer.split(":")[:-1]),
set_node_introducer.split(":")[-1],
)
config["full_node"]["introducer_peer"]["host"] = host
config["full_node"]["introducer_peer"]["port"] = int(port)
@ -79,34 +27,52 @@ def configure(args, parser):
change_made = True
except ValueError:
print("Node introducer address must be in format [IP:Port]")
if args.set_fullnode_port:
config["full_node"]["port"] = int(args.set_fullnode_port)
config["full_node"]["introducer_peer"]["port"] = int(args.set_fullnode_port)
config["farmer"]["full_node_peer"]["port"] = int(args.set_fullnode_port)
config["timelord"]["full_node_peer"]["port"] = int(args.set_fullnode_port)
config["wallet"]["full_node_peer"]["port"] = int(args.set_fullnode_port)
config["wallet"]["introducer_peer"]["port"] = int(args.set_fullnode_port)
config["introducer"]["port"] = int(args.set_fullnode_port)
if set_fullnode_port:
config["full_node"]["port"] = int(set_fullnode_port)
config["full_node"]["introducer_peer"]["port"] = int(set_fullnode_port)
config["farmer"]["full_node_peer"]["port"] = int(set_fullnode_port)
config["timelord"]["full_node_peer"]["port"] = int(set_fullnode_port)
config["wallet"]["full_node_peer"]["port"] = int(set_fullnode_port)
config["wallet"]["introducer_peer"]["port"] = int(set_fullnode_port)
config["introducer"]["port"] = int(set_fullnode_port)
print("Default full node port updated.")
change_made = True
if args.set_log_level:
if set_log_level:
levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]
if args.set_log_level in levels:
config["logging"]["log_level"] = args.set_log_level
if set_log_level in levels:
config["logging"]["log_level"] = set_log_level
print(f"Logging level updated. Check {DEFAULT_ROOT_PATH}/log/debug.log")
change_made = True
else:
print(f"Logging level not updated. Use one of: {levels}")
if args.enable_upnp is not None:
config["full_node"]["enable_upnp"] = str2bool(args.enable_upnp)
if str2bool(args.enable_upnp):
if enable_upnp is not None:
config["full_node"]["enable_upnp"] = str2bool(enable_upnp)
if str2bool(enable_upnp):
print("uPnP enabled.")
else:
print("uPnP disabled.")
change_made = True
if change_made:
print("Restart any running chia services for changes to take effect.")
save_config(args.root_path, "config.yaml", config)
else:
help_message()
save_config(root_path, "config.yaml", config)
return 0
@click.command("configure", short_help="modify configuration")
@click.option("--set-node-introducer", help="Set the introducer for node - IP:Port.", type=str)
@click.option(
"--set-fullnode-port",
help="Set the port to use for the fullnode, useful for beta testing.",
type=str,
)
@click.option(
"--set-log-level",
"--log-level",
"-log-level",
help="Set the instance log level.",
type=click.Choice(["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"]),
)
@click.option("--enable-upnp", "--upnp", "-upnp", help="Enable or disable uPnP.", type=click.Choice(["true", "false"]))
@click.pass_context
def configure_cmd(ctx, set_node_introducer, set_fullnode_port, set_log_level, enable_upnp):
configure(ctx.obj["root_path"], set_node_introducer, set_fullnode_port, set_log_level, enable_upnp)

View file

@ -1,15 +1,15 @@
import click
from src import __version__
from pathlib import Path
import os
import shutil
from argparse import Namespace, ArgumentParser
from typing import List, Dict, Any, Tuple
from src.util.default_root import DEFAULT_ROOT_PATH
from src.util.keychain import Keychain
from src.util.config import unflatten_properties
from pathlib import Path
from src.consensus.coinbase import create_puzzlehash_for_pk
from src.util.ints import uint32
@ -31,35 +31,6 @@ private_node_names = {"full_node", "wallet", "farmer", "harvester", "timelord",
public_node_names = {"full_node", "wallet", "farmer", "introducer", "timelord"}
def help_message():
print("usage: chia init")
print(
"""
chia init (migrate previous version configuration to current)
chia init -c [directory] (creates new TLS certificates signed by your CA in [directory])
Follow these steps to create new certifcates for a remote harvester:
- Make a copy of your Farming Machine CA directory: ~/.chia/[version]/config/ssl/ca
- Shut down all chia daemon processes with `chia stop all -d`
- Run `chia init -c [directory]` on your remote harvester,
where [directory] is the the copy of your Farming Machine CA directory
- Get more details on remote harvester on Chia wiki:
https://github.com/Chia-Network/chia-blockchain/wiki/Farming-on-many-machines
"""
)
def make_parser(parser):
parser.add_argument(
"-c",
"--create_certs",
help="Create new SSL certificates based on CA in [directory]",
type=Path,
default=None,
)
parser.set_defaults(function=init)
parser.print_help = lambda self=parser: help_message()
def dict_add_new_default(updated: Dict, default: Dict, do_not_migrate_keys: Dict[str, Any]):
for k in do_not_migrate_keys:
if k in updated and do_not_migrate_keys[k] == "":
@ -255,24 +226,24 @@ def generate_ssl_for_nodes(ssl_dir: Path, ca_crt: bytes, ca_key: bytes, private:
generate_ca_signed_cert(ca_crt, ca_key, crt_path, key_path)
def init(args: Namespace, parser: ArgumentParser):
if args.create_certs is not None:
if args.root_path.exists():
if os.path.isdir(args.create_certs):
ca_dir: Path = args.root_path / "config/ssl/ca"
def init(create_certs: Path, root_path: Path):
if create_certs is not None:
if root_path.exists():
if os.path.isdir(create_certs):
ca_dir: Path = root_path / "config/ssl/ca"
if ca_dir.exists():
print(f"Deleting your OLD CA in {ca_dir}")
shutil.rmtree(ca_dir)
print(f"Copying your CA from {args.create_certs} to {ca_dir}")
copy_files_rec(args.create_certs, ca_dir)
create_all_ssl(args.root_path)
print(f"Copying your CA from {create_certs} to {ca_dir}")
copy_files_rec(create_certs, ca_dir)
create_all_ssl(root_path)
else:
print(f"** Directory {args.create_certs} does not exist **")
print(f"** Directory {create_certs} does not exist **")
else:
print(f"** {args.root_path} does not exist **")
print(f"** {root_path} does not exist **")
print("** please run `chia init` to migrate or create new config files **")
else:
return chia_init(args.root_path)
return chia_init(root_path)
def chia_version_number() -> Tuple[str, str, str, str]:
@ -442,5 +413,30 @@ def chia_init(root_path: Path):
return 0
@click.command("init", short_help="create or migrate to current")
@click.option(
"--create-certs",
"-c",
default=None,
help="Create new SSL certificates based on CA in [directory]",
type=click.Path(),
)
@click.pass_context
def init_cmd(ctx: click.Context, create_certs: str):
"""
Create a new configuration or migrate from previous versions to current
\b
Follow these steps to create new certifcates for a remote harvester:
- Make a copy of your Farming Machine CA directory: ~/.chia/[version]/config/ssl/ca
- Shut down all chia daemon processes with `chia stop all -d`
- Run `chia init -c [directory]` on your remote harvester,
where [directory] is the the copy of your Farming Machine CA directory
- Get more details on remote harvester on Chia wiki:
https://github.com/Chia-Network/chia-blockchain/wiki/Farming-on-many-machines
"""
init(Path(create_certs) if create_certs is not None else None, ctx.obj["root_path"])
if __name__ == "__main__":
chia_init(DEFAULT_ROOT_PATH)

View file

@ -1,3 +1,5 @@
import click
from pathlib import Path
from typing import List
@ -18,97 +20,6 @@ from src.wallet.derive_keys import (
from src.util.ints import uint32
from src.consensus.coinbase import create_puzzlehash_for_pk
command_list = [
"generate",
"generate_and_print",
"show",
"add",
"delete",
"delete_all",
"sign",
"verify",
]
def help_message():
print("usage: chia keys command")
print(f"command can be any of {command_list}")
print("")
print("chia keys generate (generates and adds a key to keychain)")
print("chia keys generate_and_print (generates but does NOT add to keychain)")
print("chia keys show (displays all the keys in keychain)")
print("chia keys add -m [24 words] (add a private key through the mnemonic)")
print("chia keys delete -f [fingerprint] (delete a key by it's pk fingerprint in hex form)")
print("chia keys delete_all (delete all private keys in keychain)")
print("chia keys sign -f [fingerprint] -t [hd_path] -d [message] (sign a message with a private key)")
print("chia keys verify -p [public_key] -d [message] -s [signature] (verify a signature with a pk)")
def make_parser(parser):
parser.add_argument(
"-m",
"--mnemonic",
type=str,
nargs=24,
default=None,
help="Enter mnemonic you want to use",
)
parser.add_argument(
"-k",
"--key",
type=str,
default=None,
help="Enter the raw private key in hex",
)
parser.add_argument(
"-f",
"--fingerprint",
type=int,
default=None,
help="Enter the fingerprint of the key you want to use",
)
parser.add_argument(
"-t",
"--hd_path",
type=str,
default=None,
help="Enter the HD path in the form 'm/12381/8444/n/n'",
)
parser.add_argument(
"-d",
"--message",
type=str,
default=None,
help="Enter the message to sign in UTF-8",
)
parser.add_argument(
"-p",
"--public_key",
type=str,
default=None,
help="Enter the pk in hex",
)
parser.add_argument(
"-s",
"--signature",
type=str,
default=None,
help="Enter the signature in hex",
)
parser.add_argument(
"command",
help=f"Command can be any one of {command_list}",
type=str,
nargs="?",
)
parser.set_defaults(function=handler)
parser.print_help = lambda self=parser: help_message()
keychain: Keychain = Keychain()
@ -135,7 +46,7 @@ def generate_and_add():
add_private_key_seed(mnemonic)
def add_private_key_seed(mnemonic):
def add_private_key_seed(mnemonic: str):
"""
Add a private key seed to the keyring, with the given mnemonic.
"""
@ -186,39 +97,18 @@ def show_all_keys():
print(mnemonic)
def delete(args):
def delete(fingerprint: int):
"""
Delete a key by it's public key fingerprint (which is an int).
"""
if args.fingerprint is None:
print("Please specify the fingerprint argument -f")
quit()
fingerprint = args.fingerprint
assert fingerprint is not None
print(f"Deleting private_key with fingerprint {fingerprint}")
keychain.delete_key_by_fingerprint(fingerprint)
def sign(args):
if args.message is None:
print("Please specify the message argument -d")
quit()
if args.fingerprint is None or args.hd_path is None:
print("Please specify the fingerprint argument -f and hd_path argument -t")
quit()
message = args.message
assert message is not None
def sign(message: str, fingerprint: int, hd_path: str):
k = Keychain()
private_keys = k.get_all_private_keys()
fingerprint = args.fingerprint
assert fingerprint is not None
hd_path = args.hd_path
assert hd_path is not None
path: List[uint32] = [uint32(int(i)) for i in hd_path.split("/") if i != "m"]
for sk, _ in private_keys:
if sk.get_g1().get_fingerprint() == fingerprint:
@ -230,55 +120,85 @@ def sign(args):
print(f"Fingerprint {fingerprint} not found in keychain")
def verify(args):
if args.message is None:
print("Please specify the message argument -d")
quit()
if args.public_key is None:
print("Please specify the public_key argument -p")
quit()
if args.signature is None:
print("Please specify the signature argument -s")
quit()
assert args.message is not None
assert args.public_key is not None
assert args.signature is not None
message = bytes(args.message, "utf-8")
public_key = G1Element.from_bytes(bytes.fromhex(args.public_key))
signature = G2Element.from_bytes(bytes.fromhex(args.signature))
print(AugSchemeMPL.verify(public_key, message, signature))
def verify(message: str, public_key: str, signature: str):
messageBytes = bytes(message, "utf-8")
public_key = G1Element.from_bytes(bytes.fromhex(public_key))
signature = G2Element.from_bytes(bytes.fromhex(signature))
print(AugSchemeMPL.verify(public_key, messageBytes, signature))
def handler(args, parser):
if args.command is None or len(args.command) < 1:
help_message()
parser.exit(1)
root_path: Path = args.root_path
@click.group("keys", short_help="manage your keys")
@click.pass_context
def keys_cmd(ctx: click.Context):
"""Create, delete, view and use your key pairs"""
root_path: Path = ctx.obj["root_path"]
if not root_path.is_dir():
raise RuntimeError("Please initialize (or migrate) your config directory with chia init.")
command = args.command
if command not in command_list:
help_message()
parser.exit(1)
if command == "generate":
@keys_cmd.command("generate", short_help="generates and adds a key to keychain")
@click.pass_context
def generate_cmd(ctx: click.Context):
generate_and_add()
check_keys(root_path)
elif command == "show":
check_keys(ctx.obj["root_path"])
@keys_cmd.command("show", short_help="displays all the keys in keychain")
def show_cmd():
show_all_keys()
elif command == "add":
add_private_key_seed(" ".join(args.mnemonic))
check_keys(root_path)
elif command == "delete":
delete(args)
check_keys(root_path)
elif command == "delete_all":
@keys_cmd.command("add", short_help="add a private key through the mnemonic")
@click.option("--mnemonic", "-m", help="Enter mnemonic you want to use", type=str)
@click.pass_context
def add_cmd(ctx: click.Context, mnemonic: str):
add_private_key_seed(mnemonic)
check_keys(ctx.obj["root_path"])
@keys_cmd.command("delete", short_help="delete a key by it's pk fingerprint in hex form")
@click.option(
"--fingerprint",
"-f",
default=None,
help="Enter the fingerprint of the key you want to use",
type=int,
required=True,
)
@click.pass_context
def delete_cmd(ctx: click.Context, fingerprint: int):
delete(fingerprint)
check_keys(ctx.obj["root_path"])
@keys_cmd.command("delete_all", short_help="delete all private keys in keychain")
def delete_all_cmd():
keychain.delete_all_keys()
if command == "generate_and_print":
@keys_cmd.command("generate_and_print", short_help="generates but does NOT add to keychain")
def generate_and_print_cmd():
generate_and_print()
if command == "sign":
sign(args)
if command == "verify":
verify(args)
@keys_cmd.command("sign", short_help="sign a message with a private key")
@click.option("--message", "-d", default=None, help="Enter the message to sign in UTF-8", type=str, required=True)
@click.option(
"--fingerprint",
"-f",
default=None,
help="Enter the fingerprint of the key you want to use",
type=int,
required=True,
)
@click.option("--hd_path", "-t", help="Enter the HD path in the form 'm/12381/8444/n/n'", type=str, required=True)
def sing_cmd(message: str, fingerprint: int, hd_path: str):
sign(message, fingerprint, hd_path)
@keys_cmd.command("verify", short_help="verify a signature with a pk")
@click.option("--message", "-d", default=None, help="Enter the message to sign in UTF-8", type=str, required=True)
@click.option("--public_key", "-p", default=None, help="Enter the pk in hex", type=str, required=True)
@click.option("--signature", "-s", default=None, help="Enter the signature in hex", type=str, required=True)
def verify_cmd(message: str, public_key: str, signature: str):
verify(message, public_key, signature)

View file

@ -1,64 +1,27 @@
import click
import aiohttp
import asyncio
import time
from time import struct_time, localtime
from src.util.config import load_config
from src.util.default_root import DEFAULT_ROOT_PATH
from src.util.byte_types import hexstr_to_bytes
from src.util.ints import uint16
from src.rpc.full_node_rpc_client import FullNodeRpcClient
def make_parser(parser):
parser.add_argument(
"-d",
"--delta-block-height",
help="Compare a block X blocks older to estimate total network space. "
+ "Defaults to 192 blocks (~1 hour) and Peak block as the starting block. "
+ "Use --start BLOCK_HEIGHT to specify starting block. "
+ "Use 1000 blocks to replicate the GUI estimate.",
type=str,
default="192",
)
parser.add_argument(
"-s",
"--start",
help="Newest block used to calculate estimated total network space. Defaults to Peak block.",
type=str,
default="",
)
parser.add_argument(
"-p",
"--rpc-port",
help="Set the port where the Full Node is hosting the RPC interface."
+ "See the rpc_port under full_node in config.yaml. Defaults to 8555.",
type=int,
)
parser.set_defaults(function=netspace)
def human_local_time(timestamp):
time_local = struct_time(localtime(timestamp))
return time.strftime("%a %b %d %Y %T %Z", time_local)
async def netstorge_async(args, parser):
async def netstorge_async(rpc_port: int, delta_block_height: str, start: str) -> None:
"""
Calculates the estimated space on the network given two block header hases
# TODO: add help on failure/no args
"""
try:
config = load_config(DEFAULT_ROOT_PATH, "config.yaml")
self_hostname = config["self_hostname"]
if "rpc_port" not in args or args.rpc_port is None:
if rpc_port is None:
rpc_port = config["full_node"]["rpc_port"]
else:
rpc_port = args.rpc_port
client = await FullNodeRpcClient.create(self_hostname, rpc_port, DEFAULT_ROOT_PATH, config)
client = await FullNodeRpcClient.create(self_hostname, uint16(rpc_port), DEFAULT_ROOT_PATH, config)
if args.delta_block_height:
if args.start == "":
if delta_block_height:
if start == "":
blockchain_state = await client.get_blockchain_state()
if blockchain_state["peak"] is None:
print("No blocks in blockchain")
@ -68,9 +31,9 @@ async def netstorge_async(args, parser):
newer_block_height = blockchain_state["peak"].height
else:
newer_block = await client.get_block_record(hexstr_to_bytes(args.start))
newer_block = await client.get_block_record(hexstr_to_bytes(start))
if newer_block is None:
print("Block header hash", args.start, "not found.")
print("Block header hash", start, "not found.")
client.close()
await client.await_closed()
return None
@ -79,7 +42,7 @@ async def netstorge_async(args, parser):
newer_block_height = newer_block.height
newer_block_header = await client.get_block_record_by_height(newer_block_height)
older_block_height = max(0, newer_block_height - int(args.delta_block_height))
older_block_height = max(0, newer_block_height - int(delta_block_height))
older_block_header = await client.get_block_record_by_height(older_block_height)
network_space_bytes_estimate = await client.get_network_space(
newer_block_header.header_hash, older_block_header.header_hash
@ -106,7 +69,7 @@ async def netstorge_async(args, parser):
except Exception as e:
if isinstance(e, aiohttp.client_exceptions.ClientConnectorError):
print(f"Connection error. Check if full node rpc is running at {args.rpc_port}")
print(f"Connection error. Check if full node rpc is running at {rpc_port}")
else:
print(f"Exception {e}")
@ -114,5 +77,39 @@ async def netstorge_async(args, parser):
await client.await_closed()
def netspace(args, parser):
return asyncio.run(netstorge_async(args, parser))
@click.command("netspace", short_help="estimate space on the network")
@click.option(
"-p",
"--rpc-port",
help=(
"Set the port where the Full Node is hosting the RPC interface. "
"See the rpc_port under full_node in config.yaml. "
"[default: 8555]"
),
type=int,
show_default=True,
)
@click.option(
"-d",
"--delta-block-height",
help=(
"Compare a block X blocks older to estimate total network space. "
"Defaults to 192 blocks (~1 hour) and Peak block as the starting block. "
"Use --start BLOCK_HEIGHT to specify starting block. "
"Use 1000 blocks to replicate the GUI estimate."
),
type=str,
default="192",
)
@click.option(
"-s",
"--start",
help="Newest block used to calculate estimated total network space. Defaults to Peak block.",
type=str,
default="",
)
def netspace_cmd(rpc_port: int, delta_block_height: str, start: str) -> None:
"""
Calculates the estimated space on the network given two block header hases.
"""
asyncio.run(netstorge_async(rpc_port, delta_block_height, start))

View file

@ -1,3 +1,5 @@
import click
from pathlib import Path
import logging
from src.plotting.plot_tools import (
@ -13,154 +15,7 @@ from src.util.logging import initialize_logging
log = logging.getLogger(__name__)
command_list = ["create", "check", "add", "remove", "show"]
def help_message():
print("usage: chia plots command")
print(f"command can be any of {command_list}")
print("")
print(
"chia plots create -k [size] -n [number of plots] -b [memory buffer size MiB]"
+ " -r [number of threads] -u [number of buckets] -s [stripe size]"
+ " -a [fingerprint] -f [farmer public key] -p [pool public key]"
+ " -t [tmp dir] -2 [tmp dir 2] -d [final dir] (creates plots)"
)
print("-e disables bitfield plotting")
print("-x skips adding [final dir] to harvester for farming")
print("-i [plotid] -m [memo] are available for debugging")
print("chia plots check -n [challenges] -g [string] -l (checks plots)")
print(" Default: check all plots in every directory with 30 challenges")
print(" -n: number of challenges; 0 = skip opening plot files; can be used with -l")
print(" -g: checks plots with file or directory name containing [string]")
print(" -l: list plots with duplicate IDs")
print(" Debugging options for chia plots check")
print(" --debug-show-memo: shows memo to recreate the same exact plot")
print(" --challenge-start [start]: begins at a different [start] for -n [challenges]")
print("chia plots add -d [directory] (adds a directory of plots)")
print("chia plots remove -d [directory] (removes a directory of plots from config)")
print("chia plots show (shows the directory of current plots)")
def make_parser(parser):
parser.add_argument("-k", "--size", help="Plot size", type=int, default=32)
parser.add_argument("-n", "--num", help="Number of plots or challenges", type=int, default=None)
parser.add_argument("-b", "--buffer", help="Mebibytes for sort/plot buffer", type=int, default=4608)
parser.add_argument("-r", "--num_threads", help="Number of threads to use", type=int, default=2)
parser.add_argument("-u", "--buckets", help="Number of buckets", type=int, default=0)
parser.add_argument("-s", "--stripe_size", help="Stripe size", type=int, default=0)
parser.add_argument(
"-a",
"--alt_fingerprint",
type=int,
default=None,
help="Enter the alternative fingerprint of the key you want to use",
)
parser.add_argument(
"-c",
"--pool_contract_address",
type=str,
default=None,
help=(
"Address of where the pool reward will be sent to. Only used "
"if alt_fingerprint and pool public key are None"
),
)
parser.add_argument(
"-f",
"--farmer_public_key",
help="Hex farmer public key",
type=str,
default=None,
)
parser.add_argument("-p", "--pool_public_key", help="Hex public key of pool", type=str, default=None)
parser.add_argument(
"-g",
"--grep_string",
help="Shows only plots that contain the string in the filename or directory name",
type=str,
default=None,
)
parser.add_argument(
"-t",
"--tmp_dir",
help="Temporary directory for plotting files",
type=Path,
default=Path("."),
)
parser.add_argument(
"-2",
"--tmp2_dir",
help="Second temporary directory for plotting files",
type=Path,
default=None,
)
parser.add_argument(
"-d",
"--final_dir",
help="Final directory for plots (relative or absolute)",
type=Path,
default=Path("."),
)
parser.add_argument(
"-i",
"--plotid",
help="PlotID in hex for reproducing plots (debugging only)",
type=str,
default=None,
)
parser.add_argument(
"-m",
"--memo",
help="Memo in hex for reproducing plots (debugging only)",
type=str,
default=None,
)
parser.add_argument(
"-e",
"--nobitfield",
help="Disable bitfield",
default=False,
action="store_true",
)
parser.add_argument(
"-x",
"--exclude_final_dir",
help="Skips adding [final dir] to harvester for farming",
default=False,
action="store_true",
)
parser.add_argument(
"-l",
"--list_duplicates",
help="List plots with duplicate IDs",
default=False,
action="store_true",
)
parser.add_argument(
"--debug-show-memo",
help="Shows memo to recreate the same exact plot",
default=False,
action="store_true",
)
parser.add_argument(
"--challenge-start",
help="Begins at a different [start] for -n [challenges]",
type=int,
default=None,
)
parser.add_argument(
"command",
help=f"Command can be any one of {command_list}",
type=str,
nargs="?",
)
parser.set_defaults(function=handler)
parser.print_help = lambda self=parser: help_message()
def show(root_path):
def show_plots(root_path: Path):
print("Directories where plots are being searched for:")
print("Note that subdirectories must be added manually.")
print(
@ -173,30 +28,156 @@ def show(root_path):
print(f"{str_path}")
def handler(args, parser):
if args.command is None or len(args.command) < 1:
help_message()
parser.exit(1)
root_path: Path = args.root_path
@click.group("plots", short_help="manage your plots")
@click.pass_context
def plots_cmd(ctx: click.Context):
"""Create, add, remove and check your plots"""
root_path: Path = ctx.obj["root_path"]
if not root_path.is_dir():
raise RuntimeError("Please initialize (or migrate) your config directory with chia init.")
initialize_logging("", {"log_stdout": True}, root_path)
command = args.command
if command not in command_list:
help_message()
parser.exit(1)
if command == "create":
create_plots(args, root_path)
elif command == "check":
check_plots(args, root_path)
elif command == "add":
str_path = args.final_dir
add_plot_directory(str_path, root_path)
elif command == "remove":
str_path = args.final_dir
remove_plot_directory(str_path, root_path)
elif command == "show":
show(root_path)
@plots_cmd.command("create", short_help="creates plots")
@click.option("-k", "--size", help="Plot size", type=int, default=32, show_default=True)
@click.option("-n", "--num", help="Number of plots or challenges", type=int, default=1, show_default=True)
@click.option("-b", "--buffer", help="Megabytes for sort/plot buffer", type=int, default=4608, show_default=True)
@click.option("-r", "--num_threads", help="Number of threads to use", type=int, default=2, show_default=True)
@click.option("-u", "--buckets", help="Number of buckets", type=int, default=0)
@click.option("-s", "--stripe_size", help="Stripe size", type=int, default=0)
@click.option(
"-a",
"--alt_fingerprint",
type=int,
default=None,
help="Enter the alternative fingerprint of the key you want to use",
)
@click.option(
"-c",
"--pool_contract_address",
type=str,
default=None,
help="Address of where the pool reward will be sent to. Only used if alt_fingerprint and pool public key are None",
)
@click.option("-f", "--farmer_public_key", help="Hex farmer public key", type=str, default=None)
@click.option("-p", "--pool_public_key", help="Hex public key of pool", type=str, default=None)
@click.option(
"-t",
"--tmp_dir",
help="Temporary directory for plotting files",
type=click.Path(),
default=Path("."),
show_default=True,
)
@click.option("-2", "--tmp2_dir", help="Second temporary directory for plotting files", type=click.Path(), default=None)
@click.option(
"-d",
"--final_dir",
help="Final directory for plots (relative or absolute)",
type=click.Path(),
default=Path("."),
show_default=True,
)
@click.option("-i", "--plotid", help="PlotID in hex for reproducing plots (debugging only)", type=str, default=None)
@click.option("-m", "--memo", help="Memo in hex for reproducing plots (debugging only)", type=str, default=None)
@click.option("-e", "--nobitfield", help="Disable bitfield", default=False, is_flag=True)
@click.option(
"-x", "--exclude_final_dir", help="Skips adding [final dir] to harvester for farming", default=False, is_flag=True
)
@click.pass_context
def create_cmd(
ctx: click.Context,
size: int,
num: int,
buffer: int,
num_threads: int,
buckets: int,
stripe_size: int,
alt_fingerprint: int,
pool_contract_address: str,
farmer_public_key: str,
pool_public_key: str,
tmp_dir: str,
tmp2_dir: str,
final_dir: str,
plotid: str,
memo: str,
nobitfield: bool,
exclude_final_dir: bool,
):
class Params(object):
def __init__(self):
self.size = size
self.num = num
self.buffer = buffer
self.num_threads = num_threads
self.buckets = buckets
self.stripe_size = stripe_size
self.alt_fingerprint = alt_fingerprint
self.pool_contract_address = pool_contract_address
self.farmer_public_key = farmer_public_key
self.pool_public_key = pool_public_key
self.tmp_dir = Path(tmp_dir)
self.tmp2_dir = Path(tmp2_dir) if tmp2_dir else None
self.final_dir = Path(final_dir)
self.plotid = plotid
self.memo = memo
self.nobitfield = nobitfield
self.exclude_final_dir = exclude_final_dir
create_plots(Params(), ctx.obj["root_path"])
@plots_cmd.command("check", short_help="checks plots")
@click.option("-n", "--num", help="Number of plots or challenges", type=int, default=None)
@click.option(
"-g",
"--grep_string",
help="Shows only plots that contain the string in the filename or directory name",
type=str,
default=None,
)
@click.option("-l", "--list_duplicates", help="List plots with duplicate IDs", default=False, is_flag=True)
@click.option("--debug-show-memo", help="Shows memo to recreate the same exact plot", default=False, is_flag=True)
@click.option("--challenge-start", help="Begins at a different [start] for -n [challenges]", type=int, default=None)
@click.pass_context
def check_cmd(
ctx: click.Context, num: int, grep_string: str, list_duplicates: bool, debug_show_memo: bool, challenge_start: int
):
check_plots(ctx.obj["root_path"], num, challenge_start, grep_string, list_duplicates, debug_show_memo)
@plots_cmd.command("add", short_help="adds a directory of plots")
@click.option(
"-d",
"--final_dir",
help="Final directory for plots (relative or absolute)",
type=click.Path(),
default=".",
show_default=True,
)
@click.pass_context
def add_cmd(ctx: click.Context, final_dir: str):
add_plot_directory(Path(final_dir), ctx.obj["root_path"])
print(f'Added plot directory "{final_dir}".')
@plots_cmd.command("remove", short_help="removes a directory of plots from config")
@click.option(
"-d",
"--final_dir",
help="Final directory for plots (relative or absolute)",
type=click.Path(),
default=".",
show_default=True,
)
@click.pass_context
def remove_cmd(ctx: click.Context, final_dir: str):
remove_plot_directory(Path(final_dir), ctx.obj["root_path"])
print(f'Removed plot directory "{final_dir}".')
@plots_cmd.command("show", short_help="shows the directory of current plots")
@click.pass_context
def show_cmd(ctx: click.Context):
show_plots(ctx.obj["root_path"])

View file

@ -1,11 +0,0 @@
import asyncio
from src.daemon.server import async_run_daemon
def make_parser(parser):
parser.set_defaults(function=run_daemon)
def run_daemon(args, parser):
return asyncio.get_event_loop().run_until_complete(async_run_daemon(args.root_path))

View file

@ -1,3 +1,4 @@
import click
import traceback
import aiohttp
@ -12,115 +13,37 @@ from src.server.outbound_message import NodeType
from src.types.full_block import FullBlock
from src.rpc.full_node_rpc_client import FullNodeRpcClient
from src.util.byte_types import hexstr_to_bytes
from src.util.config import str2bool
from src.util.config import load_config
from src.util.default_root import DEFAULT_ROOT_PATH
from src.util.bech32m import encode_puzzle_hash
from src.util.ints import uint16
def make_parser(parser):
parser.add_argument(
"-s",
"--state",
help="Show the current state of the blockchain.",
type=str2bool,
nargs="?",
const=True,
default=False,
)
parser.add_argument(
"-c",
"--connections",
help="List nodes connected to this Full Node.",
type=str2bool,
nargs="?",
const=True,
default=False,
)
parser.add_argument(
"-b",
"--block-by-header-hash",
help="Look up a block by block header hash.",
type=str,
default="",
)
parser.add_argument(
"-bh",
"--block-header-hash-by-height",
help="Look up a block header hash by block height.",
type=str,
default="",
)
parser.add_argument(
"-a",
"--add-connection",
help="Connect to another Full Node by ip:port",
type=str,
default="",
)
parser.add_argument(
"-r",
"--remove-connection",
help="Remove a Node by the first 8 characters of NodeID",
type=str,
default="",
)
parser.add_argument(
"-e",
"--exit-node",
help="Shut down the running Full Node",
nargs="?",
const=True,
default=False,
)
parser.add_argument(
"-p",
"--rpc-port",
help="Set the port where the Full Node is hosting the RPC interface."
+ " See the rpc_port under full_node in config.yaml."
+ "Defaults to 8555",
type=int,
default=8555,
)
parser.add_argument(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface."
+ " See the rpc_port under wallet in config.yaml."
+ "Defaults to 9256",
type=int,
default=9256,
)
parser.set_defaults(function=show)
async def show_async(args, parser):
async def show_async(
rpc_port: int,
state: bool,
show_connections: bool,
exit_node: bool,
add_connection: str,
remove_connection: str,
block_header_hash_by_height: str,
block_by_header_hash: str,
) -> None:
# TODO read configuration for rpc_port instead of assuming default
try:
config = load_config(DEFAULT_ROOT_PATH, "config.yaml")
self_hostname = config["self_hostname"]
if "rpc_port" not in args or args.rpc_port is None:
if rpc_port is None:
rpc_port = config["full_node"]["rpc_port"]
else:
rpc_port = args.rpc_port
client = await FullNodeRpcClient.create(self_hostname, rpc_port, DEFAULT_ROOT_PATH, config)
client = await FullNodeRpcClient.create(self_hostname, uint16(rpc_port), DEFAULT_ROOT_PATH, config)
if args.state:
if state:
blockchain_state = await client.get_blockchain_state()
if blockchain_state is None:
return "There is no blockchain found yet. Try again shortly."
print("There is no blockchain found yet. Try again shortly.")
return
peak: Optional[BlockRecord] = blockchain_state["peak"]
difficulty = blockchain_state["difficulty"]
sub_slot_iters = blockchain_state["sub_slot_iters"]
@ -140,7 +63,7 @@ async def show_async(args, parser):
)
if synced:
print("Current Blockchain Status: Full Node Synced")
print("\nPeak: Hash:", peak.header_hash)
print("\nPeak: Hash:", peak.header_hash if peak is not None else "")
elif peak is not None:
print(f"Current Blockchain Status: Not Synced. Peak height: {peak.height}")
else:
@ -156,11 +79,11 @@ async def show_async(args, parser):
while curr is not None and not curr.is_transaction_block:
curr = await client.get_block_record(curr.prev_hash)
peak_time = curr.timestamp
peak_time = struct_time(localtime(peak_time))
peak_time_struct = struct_time(localtime(peak_time))
print(
" Time:",
f"{time.strftime('%a %b %d %Y %T %Z', peak_time)}",
f"{time.strftime('%a %b %d %Y %T %Z', peak_time_struct)}",
f" Height: {peak.height:>10}\n",
)
@ -188,10 +111,10 @@ async def show_async(args, parser):
else:
print("Blockchain has no blocks yet")
# if called together with connections, leave a blank line
if args.connections:
# if called together with show_connections, leave a blank line
if show_connections:
print("")
if args.connections:
if show_connections:
connections = await client.get_connections()
print("Connections:")
print(
@ -235,18 +158,18 @@ async def show_async(args, parser):
)
print(con_str)
# if called together with state, leave a blank line
if args.state:
if state:
print("")
if args.exit_node:
if exit_node:
node_stop = await client.stop_node()
print(node_stop, "Node stopped.")
if args.add_connection:
if ":" not in args.add_connection:
if add_connection:
if ":" not in add_connection:
print("Enter a valid IP and port in the following format: 10.5.4.3:8000")
else:
ip, port = (
":".join(args.add_connection.split(":")[:-1]),
args.add_connection.split(":")[-1],
":".join(add_connection.split(":")[:-1]),
add_connection.split(":")[-1],
)
print(f"Connecting to {ip}, {port}")
try:
@ -254,34 +177,34 @@ async def show_async(args, parser):
except Exception:
# TODO: catch right exception
print(f"Failed to connect to {ip}:{port}")
if args.remove_connection:
if remove_connection:
result_txt = ""
if len(args.remove_connection) != 8:
if len(remove_connection) != 8:
result_txt = "Invalid NodeID. Do not include '.'."
else:
connections = await client.get_connections()
for con in connections:
if args.remove_connection == con["node_id"].hex()[:8]:
print("Attempting to disconnect", "NodeID", args.remove_connection)
if remove_connection == con["node_id"].hex()[:8]:
print("Attempting to disconnect", "NodeID", remove_connection)
try:
await client.close_connection(con["node_id"])
except Exception:
result_txt = f"Failed to disconnect NodeID {args.remove_connection}"
result_txt = f"Failed to disconnect NodeID {remove_connection}"
else:
result_txt = f"NodeID {args.remove_connection}... {NodeType(con['type']).name} "
result_txt = f"NodeID {remove_connection}... {NodeType(con['type']).name} "
f"{con['peer_host']} disconnected."
elif result_txt == "":
result_txt = f"NodeID {args.remove_connection}... not found."
result_txt = f"NodeID {remove_connection}... not found."
print(result_txt)
if args.block_header_hash_by_height != "":
block_header = await client.get_block_record_by_height(args.block_header_hash_by_height)
if block_header_hash_by_height != "":
block_header = await client.get_block_record_by_height(block_header_hash_by_height)
if block_header is not None:
print(f"Header hash of block {args.block_header_hash_by_height}: " f"{block_header.header_hash.hex()}")
print(f"Header hash of block {block_header_hash_by_height}: " f"{block_header.header_hash.hex()}")
else:
print("Block height", args.block_header_hash_by_height, "not found.")
if args.block_by_header_hash != "":
block: Optional[BlockRecord] = await client.get_block_record(hexstr_to_bytes(args.block_by_header_hash))
full_block: Optional[FullBlock] = await client.get_block(hexstr_to_bytes(args.block_by_header_hash))
print("Block height", block_header_hash_by_height, "not found.")
if block_by_header_hash != "":
block: Optional[BlockRecord] = await client.get_block_record(hexstr_to_bytes(block_by_header_hash))
full_block: Optional[FullBlock] = await client.get_block(hexstr_to_bytes(block_by_header_hash))
# Would like to have a verbose flag for this
if block is not None:
assert full_block is not None
@ -292,9 +215,17 @@ async def show_async(args, parser):
difficulty = block.weight
if block.is_transaction_block:
assert full_block.transactions_info is not None
block_time = struct_time(localtime(full_block.foliage_transaction_block.timestamp))
block_time = struct_time(
localtime(
full_block.foliage_transaction_block.timestamp
if full_block.foliage_transaction_block
else None
)
)
block_time_string = time.strftime("%a %b %d %Y %T %Z", block_time)
cost = full_block.transactions_info.cost
cost = str(full_block.transactions_info.cost)
tx_filter_hash = "Not a transaction block"
if full_block.foliage_transaction_block:
tx_filter_hash = full_block.foliage_transaction_block.filter_hash
else:
block_time_string = "Not a transaction block"
@ -325,11 +256,11 @@ async def show_async(args, parser):
f"Fees Amount {block.fees}\n"
)
else:
print("Block with header hash", args.block_header_hash_by_height, "not found.")
print("Block with header hash", block_header_hash_by_height, "not found.")
except Exception as e:
if isinstance(e, aiohttp.client_exceptions.ClientConnectorError):
print(f"Connection error. Check if full node rpc is running at {args.rpc_port}")
print(f"Connection error. Check if full node rpc is running at {rpc_port}")
print("This is normal if full node is still starting up.")
else:
tb = traceback.format_exc()
@ -339,5 +270,59 @@ async def show_async(args, parser):
await client.await_closed()
def show(args, parser):
return asyncio.run(show_async(args, parser))
@click.command("show", short_help="show node information")
@click.option(
"-p",
"--rpc-port",
help=(
"Set the port where the Full Node is hosting the RPC interface. "
"See the rpc_port under full_node in config.yaml."
),
type=int,
default=8555,
show_default=True,
)
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml.",
type=int,
default=9256,
show_default=True,
)
@click.option("-s", "--state", help="Show the current state of the blockchain.", is_flag=True, type=bool, default=False)
@click.option(
"-c", "--connections", help="List nodes connected to this Full Node.", is_flag=True, type=bool, default=False
)
@click.option("-e", "--exit-node", help="Shut down the running Full Node", is_flag=True, default=False)
@click.option("-a", "--add-connection", help="Connect to another Full Node by ip:port", type=str, default="")
@click.option(
"-r", "--remove-connection", help="Remove a Node by the first 8 characters of NodeID", type=str, default=""
)
@click.option(
"-bh", "--block-header-hash-by-height", help="Look up a block header hash by block height.", type=str, default=""
)
@click.option("-b", "--block-by-header-hash", help="Look up a block by block header hash.", type=str, default="")
def show_cmd(
rpc_port: int,
wallet_rpc_port: int,
state: bool,
connections: bool,
exit_node: bool,
add_connection: str,
remove_connection: str,
block_header_hash_by_height: str,
block_by_header_hash: str,
) -> None:
asyncio.run(
show_async(
rpc_port,
state,
connections,
exit_node,
add_connection,
remove_connection,
block_header_hash_by_height,
block_by_header_hash,
)
)

View file

@ -1,42 +1,29 @@
import click
import asyncio
import os
import subprocess
from pathlib import Path
from typing import Optional
from src.daemon.client import connect_to_daemon_and_validate
from src.daemon.client import connect_to_daemon_and_validate, DaemonProxy
from src.util.service_groups import all_groups, services_for_groups
def make_parser(parser):
parser.add_argument(
"-r",
"--restart",
action="store_true",
help="Restart of running processes",
)
parser.add_argument(
"group",
choices=all_groups(),
type=str,
nargs="+",
)
parser.set_defaults(function=start)
def launch_start_daemon(root_path):
def launch_start_daemon(root_path: Path) -> subprocess.Popen:
os.environ["CHIA_ROOT"] = str(root_path)
# TODO: use startupinfo=subprocess.DETACHED_PROCESS on windows
process = subprocess.Popen("chia run_daemon".split(), stdout=subprocess.PIPE)
return process
async def create_start_daemon_connection(root_path):
async def create_start_daemon_connection(root_path: Path) -> Optional[DaemonProxy]:
connection = await connect_to_daemon_and_validate(root_path)
if connection is None:
print("Starting daemon")
# launch a daemon
process = launch_start_daemon(root_path)
# give the daemon a chance to start up
if process.stdout:
process.stdout.readline()
await asyncio.sleep(1)
# it prints "daemon: listening"
@ -46,16 +33,16 @@ async def create_start_daemon_connection(root_path):
return None
async def async_start(args, parser):
daemon = await create_start_daemon_connection(args.root_path)
async def async_start(root_path: Path, group: str, restart: bool) -> None:
daemon = await create_start_daemon_connection(root_path)
if daemon is None:
print("failed to create the chia start daemon")
return 1
return
for service in services_for_groups(args.group):
for service in services_for_groups(group):
if await daemon.is_running(service_name=service):
print(f"{service}: ", end="", flush=True)
if args.restart:
if restart:
if not await daemon.is_running(service_name=service):
print("not running")
elif await daemon.stop_service(service_name=service):
@ -77,5 +64,9 @@ async def async_start(args, parser):
await daemon.close()
def start(args, parser):
return asyncio.get_event_loop().run_until_complete(async_start(args, parser))
@click.command("start", short_help="start service groups")
@click.option("-r", "--restart", is_flag=True, type=bool, help="Restart of running processes")
@click.argument("group", type=click.Choice(all_groups()), nargs=-1, required=True)
@click.pass_context
def start_cmd(ctx: click.Context, restart: bool, group: str) -> None:
asyncio.get_event_loop().run_until_complete(async_start(ctx.obj["root_path"], group, restart))

View file

@ -1,50 +1,19 @@
import click
import sys
import asyncio
from pathlib import Path
from src.daemon.client import connect_to_daemon_and_validate
from src.util.service_groups import all_groups, services_for_groups
def make_parser(parser):
parser.add_argument(
"-d",
"--daemon",
action="store_true",
help="Stop daemon",
)
parser.add_argument(
"group",
choices=all_groups(),
type=str,
nargs="+",
default=None,
)
parser.set_defaults(function=stop)
async def stop_daemon(daemon):
service = "daemon"
print(f"{service}: ", end="", flush=True)
if daemon is None:
print("not running")
return
r = await daemon.exit()
exited = r["data"]["success"]
if exited:
print("Daemon stopped")
else:
error = r["data"]["error"]
print(f"error: {error}")
async def async_stop(args, parser):
daemon = await connect_to_daemon_and_validate(args.root_path)
async def async_stop(root_path: Path, group: str, stop_daemon: bool) -> int:
daemon = await connect_to_daemon_and_validate(root_path)
if daemon is None:
print("couldn't connect to chia daemon")
return 1
if args.daemon:
if stop_daemon:
r = await daemon.exit()
await daemon.close()
print(f"daemon: {r}")
@ -52,7 +21,7 @@ async def async_stop(args, parser):
return_val = 0
for service in services_for_groups(args.group):
for service in services_for_groups(group):
print(f"{service}: ", end="", flush=True)
if not await daemon.is_running(service_name=service):
print("not running")
@ -66,5 +35,9 @@ async def async_stop(args, parser):
return return_val
def stop(args, parser):
return asyncio.get_event_loop().run_until_complete(async_stop(args, parser))
@click.command("stop", short_help="stop service groups")
@click.option("-d", "--daemon", is_flag=True, type=bool, help="Stop daemon")
@click.argument("group", type=click.Choice(all_groups()), nargs=-1, required=True)
@click.pass_context
def stop_cmd(ctx: click.Context, daemon: bool, group: str) -> None:
sys.exit(asyncio.get_event_loop().run_until_complete(async_stop(ctx.obj["root_path"], group, daemon)))

View file

@ -1,10 +0,0 @@
from src import __version__
def version(args, parser):
print(__version__)
def make_parser(parser):
parser.set_defaults(function=version)
return parser

View file

@ -1,3 +1,4 @@
import click
import sys
import time
from datetime import datetime
@ -11,77 +12,14 @@ from src.util.bech32m import encode_puzzle_hash
from src.util.byte_types import hexstr_to_bytes
from src.util.config import load_config
from src.util.default_root import DEFAULT_ROOT_PATH
from src.util.ints import uint64
from src.util.ints import uint64, uint16
from src.wallet.transaction_record import TransactionRecord
from src.wallet.util.wallet_types import WalletType
from src.cmds.units import units
from decimal import Decimal
command_list = ["send", "show", "get_transaction", "get_transactions"]
def help_message():
print("usage: chia wallet command")
print(f"command can be any of {command_list}")
print("")
print(
"chia wallet send -f [optional fingerprint] -i [optional wallet_id] -a [(T)XCH amount] -m [(T)XCH fee] "
"-t [target address]"
)
print("chia wallet show -f [optional fingerprint] -i [optional wallet_id]")
print("chia wallet get_transaction -f [optional fingerprint] -i [optional wallet_id] -tx [transaction id]")
print("chia wallet get_transactions -f [optional fingerprint] -i [optional wallet_id]")
def make_parser(parser):
parser.add_argument(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface."
+ " See the rpc_port under wallet in config.yaml."
+ "Defaults to 9256",
type=int,
default=9256,
)
parser.add_argument(
"-f",
"--fingerprint",
help="Set the fingerprint to specify which wallet to use.",
type=int,
)
parser.add_argument("-i", "--id", help="Id of the wallet to use.", type=int, default=1)
parser.add_argument(
"-a",
"--amount",
help="How much chia to send, in TXCH/XCH",
type=str,
)
parser.add_argument("-m", "--fee", help="Set the fees for the transaction.", type=str, default="0")
parser.add_argument(
"-t",
"--address",
help="Address to send the TXCH/XCH",
type=str,
)
parser.add_argument(
"-tx",
"--tx_id",
help="transaction id to search for",
type=str,
)
parser.add_argument("--verbose", "-v", action="count", default=0)
parser.add_argument(
"command",
help=f"Command can be any one of {command_list}",
type=str,
nargs="?",
)
parser.set_defaults(function=handler)
parser.print_help = lambda self=parser: help_message()
def print_transaction(tx: TransactionRecord, verbose: bool):
def print_transaction(tx: TransactionRecord, verbose: bool) -> None:
if verbose:
print(tx)
else:
@ -95,33 +33,21 @@ def print_transaction(tx: TransactionRecord, verbose: bool):
print("")
async def get_transaction(args, wallet_client, fingerprint: int):
if args.id is None:
print("Please specify a wallet id with -i")
return
else:
wallet_id = args.id
if args.tx_id is None:
print("Please specify a transaction id -tx")
return
else:
transaction_id = hexstr_to_bytes(args.tx_id)
async def get_transaction(args: dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["id"]
transaction_id = hexstr_to_bytes(args["tx_id"])
tx: TransactionRecord = await wallet_client.get_transaction(wallet_id, transaction_id=transaction_id)
print_transaction(tx, verbose=(args.verbose > 0))
print_transaction(tx, verbose=(args["verbose"] > 0))
async def get_transactions(args, wallet_client, fingerprint: int):
if args.id is None:
print("Please specify a wallet id with -i")
return
else:
wallet_id = args.id
async def get_transactions(args: dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["id"]
txs: List[TransactionRecord] = await wallet_client.get_transactions(wallet_id)
if len(txs) == 0:
print("There are no transactions to this address")
for i in range(0, len(txs), 5):
for j in range(0, 5):
print_transaction(txs[i + j], verbose=(args.verbose > 0))
print_transaction(txs[i + j], verbose=(args["verbose"] > 0))
print("Press q to quit, or c to continue")
while True:
entered_key = sys.stdin.read(1)
@ -131,27 +57,11 @@ async def get_transactions(args, wallet_client, fingerprint: int):
break
async def send(args, wallet_client, fingerprint: int):
if args.id is None:
print("Please specify a wallet id with -i")
return
else:
wallet_id = args.id
if args.amount is None:
print("Please specify an amount with -a")
return
else:
amount = Decimal(args.amount)
if args.amount is None:
print("Please specify the transaction fees with -m")
return
else:
fee = Decimal(args.fee)
if args.address is None:
print("Please specify a target address with -t")
return
else:
address = args.address
async def send(args: dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
wallet_id = args["id"]
amount = Decimal(args["amount"])
fee = Decimal(args["fee"])
address = args["address"]
print("Submitting transaction...")
final_amount = uint64(int(amount * units["chia"]))
@ -171,7 +81,7 @@ async def send(args, wallet_client, fingerprint: int):
print(f"Do 'chia wallet get_transaction -f {fingerprint} -tx 0x{tx_id}' to get status")
async def print_balances(args, wallet_client, fingerprint: int):
async def print_balances(args: dict, wallet_client: WalletRpcClient, fingerprint: int) -> None:
summaries_response = await wallet_client.get_wallets()
print(f"Wallet height: {await wallet_client.get_height_info()}")
@ -206,7 +116,7 @@ async def print_balances(args, wallet_client, fingerprint: int):
)
async def get_wallet(wallet_client, fingerprint=None) -> Optional[Tuple[WalletRpcClient, int]]:
async def get_wallet(wallet_client: WalletRpcClient, fingerprint: int = None) -> Optional[Tuple[WalletRpcClient, int]]:
fingerprints = await wallet_client.get_public_keys()
if len(fingerprints) == 0:
print("No keys loaded. Run 'chia keys generate' or import a key.")
@ -238,7 +148,9 @@ async def get_wallet(wallet_client, fingerprint=None) -> Optional[Tuple[WalletRp
continue
else:
fingerprint = fingerprints[index]
assert fingerprint is not None
log_in_response = await wallet_client.log_in(fingerprint)
if log_in_response["success"] is False:
if log_in_response["error"] == "not_initialized":
use_cloud = True
@ -277,31 +189,24 @@ async def get_wallet(wallet_client, fingerprint=None) -> Optional[Tuple[WalletRp
return wallet_client, fingerprint
async def execute_with_wallet(args, parser, function: Callable):
if args.fingerprint is None:
fingerprint = None
else:
fingerprint = args.fingerprint
async def execute_with_wallet(wallet_rpc_port: int, fingerprint: int, extra_params: dict, function: Callable) -> None:
try:
config = load_config(DEFAULT_ROOT_PATH, "config.yaml")
self_hostname = config["self_hostname"]
if "wallet_rpc_port" not in args or args.wallet_rpc_port is None:
if wallet_rpc_port is None:
wallet_rpc_port = config["wallet"]["rpc_port"]
else:
wallet_rpc_port = args.wallet_rpc_port
wallet_client = await WalletRpcClient.create(self_hostname, wallet_rpc_port, DEFAULT_ROOT_PATH, config)
wallet_client = await WalletRpcClient.create(self_hostname, uint16(wallet_rpc_port), DEFAULT_ROOT_PATH, config)
wallet_client_f = await get_wallet(wallet_client, fingerprint=fingerprint)
if wallet_client_f is None:
wallet_client.close()
await wallet_client.await_closed()
return
wallet_client, fingerprint = wallet_client_f
await function(args, wallet_client, fingerprint)
await function(extra_params, wallet_client, fingerprint)
except Exception as e:
if isinstance(e, aiohttp.client_exceptions.ClientConnectorError):
print(f"Connection error. Check if wallet is running at {args.wallet_rpc_port}")
print(f"Connection error. Check if wallet is running at {wallet_rpc_port}")
else:
print(f"Exception from 'wallet' {e}")
@ -309,20 +214,76 @@ async def execute_with_wallet(args, parser, function: Callable):
await wallet_client.await_closed()
def handler(args, parser):
if args.command is None or len(args.command) < 1:
help_message()
parser.exit(1)
command = args.command
if command not in command_list:
help_message()
parser.exit(1)
@click.group("wallet", short_help="manage your wallet")
def wallet_cmd() -> None:
pass
if command == "get_transaction":
return asyncio.run(execute_with_wallet(args, parser, get_transaction))
if command == "get_transactions":
return asyncio.run(execute_with_wallet(args, parser, get_transactions))
if command == "send":
return asyncio.run(execute_with_wallet(args, parser, send))
elif command == "show":
return asyncio.run(execute_with_wallet(args, parser, print_balances))
@wallet_cmd.command("get_transaction", short_help="get transaction")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml.",
type=int,
default=9256,
show_default=True,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use.", type=int)
@click.option("-i", "--id", help="Id of the wallet to use.", type=int, default=1, show_default=True, required=True)
@click.option("-tx", "--tx_id", help="transaction id to search for", type=str, required=True)
@click.option("--verbose", "-v", count=True, type=int)
def get_transaction_cmd(wallet_rpc_port: int, fingerprint: int, id: int, tx_id: str, verbose: int) -> None:
extra_params = {"id": id, "tx_id": tx_id, "verbose": verbose}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, get_transaction))
@wallet_cmd.command("get_transactions", short_help="get all transactions")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml.",
type=int,
default=9256,
show_default=True,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use.", type=int)
@click.option("-i", "--id", help="Id of the wallet to use.", type=int, default=1, show_default=True, required=True)
@click.option("--verbose", "-v", count=True, type=int)
def get_transactions_cmd(wallet_rpc_port: int, fingerprint: int, id: int, verbose: bool) -> None:
extra_params = {"id": id, "verbose": verbose}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, get_transactions))
@wallet_cmd.command("send", short_help="send chia to other wallet")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml.",
type=int,
default=9256,
show_default=True,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use.", type=int)
@click.option("-i", "--id", help="Id of the wallet to use.", type=int, default=1, show_default=True, required=True)
@click.option("-a", "--amount", help="How much chia to send, in TXCH/XCH", type=str, required=True)
@click.option(
"-m", "--fee", help="Set the fees for the transaction.", type=str, default="0", show_default=True, required=True
)
@click.option("-t", "--address", help="Address to send the TXCH/XCH", type=str, required=True)
def send_cmd(wallet_rpc_port: int, fingerprint: int, id: int, amount: str, fee: str, address: str) -> None:
extra_params = {"id": id, "amount": amount, "fee": fee, "address": address}
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, extra_params, send))
@wallet_cmd.command("show", short_help="show wallet information")
@click.option(
"-wp",
"--wallet-rpc-port",
help="Set the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml.",
type=int,
default=9256,
show_default=True,
)
@click.option("-f", "--fingerprint", help="Set the fingerprint to specify which wallet to use.", type=int)
def show_cmd(wallet_rpc_port: int, fingerprint: int) -> None:
asyncio.run(execute_with_wallet(wallet_rpc_port, fingerprint, {}, print_balances))

View file

@ -13,10 +13,9 @@ from src.wallet.derive_keys import master_sk_to_farmer_sk
log = logging.getLogger(__name__)
def check_plots(args, root_path):
def check_plots(root_path, num, challenge_start, grep_string, list_duplicates, debug_show_memo):
config = load_config(root_path, "config.yaml")
if args.num is not None:
num = args.num
if num is not None:
if num == 0:
log.warning("Not opening plot files")
else:
@ -28,25 +27,25 @@ def check_plots(args, root_path):
else:
num = 30
if args.challenge_start is not None:
num_start = args.challenge_start
if challenge_start is not None:
num_start = challenge_start
num_end = num_start + num
else:
num_start = 0
num_end = num
challenges = num_end - num_start
if args.grep_string is not None:
match_str = args.grep_string
if grep_string is not None:
match_str = grep_string
else:
match_str = None
if args.list_duplicates:
if list_duplicates:
log.warning("Checking for duplicate Plot IDs")
log.info("Plot filenames expected to end with -[64 char plot ID].plot")
show_memo: bool = args.debug_show_memo
show_memo: bool = debug_show_memo
if args.list_duplicates:
if list_duplicates:
plot_filenames: Dict[Path, List[Path]] = get_plot_filenames(config["harvester"])
all_filenames: List[Path] = []
for paths in plot_filenames.values():

View file

@ -78,10 +78,7 @@ def create_plots(args, root_path, use_datetime=True, test_private_keys: Optional
pool_contract_puzzle_hash = decode_puzzle_hash(args.pool_contract_address)
assert (pool_public_key is None) != (pool_contract_puzzle_hash is None)
if args.num is not None:
num = args.num
else:
num = 1
if args.size < config["min_mainnet_k_size"] and test_private_keys is None:
log.warning(f"Creating plots with size k={args.size}, which is less than the minimum required for mainnet")

View file

@ -112,7 +112,7 @@ class WalletNode:
self.logged_in_fingerprint: Optional[int] = None
self.peer_task = None
def get_key_for_fingerprint(self, fingerprint):
def get_key_for_fingerprint(self, fingerprint: Optional[int]):
private_keys = self.keychain.get_all_private_keys()
if len(private_keys) == 0:
self.log.warning("No keys present. Create keys with the UI, or with the 'chia keys' program.")