Skip to content

Commit 8ee48a7

Browse files
committed
First crack at "chia <verb>" subcommands, show and version for now.
1 parent c28b312 commit 8ee48a7

File tree

3 files changed

+281
-235
lines changed

3 files changed

+281
-235
lines changed

src/cmds/cli.py

Lines changed: 25 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,256 +1,46 @@
1-
import asyncio
21
import argparse
3-
import sys
4-
import aiohttp
5-
import time
6-
from time import struct_time, localtime
2+
import importlib
73

8-
from typing import List, Optional
4+
from src.util.config import load_config
5+
from src import __version__
96

10-
from src.server.connection import NodeType
11-
from src.types.header_block import HeaderBlock
12-
from src.rpc.rpc_client import RpcClient
13-
from src.util.byte_types import hexstr_to_bytes
14-
from src.util.config import load_config, str2bool
157

8+
SUBCOMMANDS = ["show", "version"]
169

17-
async def async_main():
18-
# a hack to generate the config.yaml file if missing
19-
load_config("config.yaml")
2010

11+
def create_parser():
2112
parser = argparse.ArgumentParser(
22-
description="Manage a Chia Full Node from the command line.",
23-
epilog="You can combine -s and -c. Try 'watch -n 10 chia -s -c' if you have 'watch' installed.",
24-
)
25-
26-
parser.add_argument(
27-
"-b",
28-
"--block_header_hash",
29-
help="Look up a block by block header hash string.",
30-
type=str,
31-
default="",
32-
)
33-
parser.add_argument(
34-
"-s",
35-
"--state",
36-
help="Show the current state of the blockchain.",
37-
type=str2bool,
38-
nargs="?",
39-
const=True,
40-
default=False,
41-
)
42-
parser.add_argument(
43-
"-c",
44-
"--connections",
45-
help="List nodes connected to this Full Node.",
46-
type=str2bool,
47-
nargs="?",
48-
const=True,
49-
default=False,
50-
)
51-
52-
parser.add_argument(
53-
"-p",
54-
"--rpc-port",
55-
help=f"Set the port where the Full Node is hosting the RPC interface. See the rpc_port "
56-
f"under full_node in config.yaml. Defaults to 8555",
57-
type=int,
58-
default=8555,
13+
description="Manage chia blockchain infrastructure (%s)." % __version__,
14+
epilog="You can combine -s and -c. Try 'watch -n 10 chia show -s -c' if you have 'watch' installed.",
5915
)
6016

61-
parser.add_argument(
62-
"-e",
63-
"--exit-node",
64-
help="Shut down the running Full Node",
65-
nargs="?",
66-
const=True,
67-
default=False,
68-
)
69-
70-
parser.add_argument(
71-
"-a",
72-
"--add-connection",
73-
help="Connect to another Full Node by ip:port",
74-
type=str,
75-
default="",
76-
)
77-
78-
parser.add_argument(
79-
"-r",
80-
"--remove-connection",
81-
help="Remove a Node by the first 10 characters of NodeID",
82-
type=str,
83-
default="",
84-
)
85-
86-
args = parser.parse_args(args=None if sys.argv[1:] else ["--help"])
17+
subparsers = parser.add_subparsers()
8718

88-
# print(args)
89-
try:
90-
client = await RpcClient.create(args.rpc_port)
19+
# this magic metaprogramming generalizes:
20+
# from src.cmds import version
21+
# new_parser = subparsers.add_parser(version)
22+
# version.version_parser(new_parser)
9123

92-
# print (dir(client))
93-
# TODO: Add other rpc calls
94-
# TODO: pretty print response
95-
if args.state:
96-
blockchain_state = await client.get_blockchain_state()
97-
lca_block = blockchain_state["lca"]
98-
tips = blockchain_state["tips"]
99-
difficulty = blockchain_state["difficulty"]
100-
ips = blockchain_state["ips"]
101-
sync_mode = blockchain_state["sync_mode"]
102-
total_iters = lca_block.data.total_iters
103-
num_blocks: int = 10
24+
for subcommand in SUBCOMMANDS:
25+
mod = importlib.import_module("src.cmds.%s" % subcommand)
26+
f = getattr(mod, "%s_parser" % subcommand)
27+
f(subparsers.add_parser(subcommand))
10428

105-
if sync_mode:
106-
sync_max_block = await client.get_heaviest_block_seen()
107-
# print (max_block)
108-
print(
109-
"Current Blockchain Status. Full Node Syncing to",
110-
sync_max_block.data.height,
111-
)
112-
else:
113-
print("Current Blockchain Status. Full Node Synced")
114-
print("Current least common ancestor ", lca_block.header_hash)
115-
# print ("LCA time",time.ctime(lca_block.data.timestamp),"LCA height:",lca_block.height)
116-
lca_time = struct_time(localtime(lca_block.data.timestamp))
117-
print(
118-
"LCA time",
119-
time.strftime("%a %b %d %Y %T %Z", lca_time),
120-
"LCA height:",
121-
lca_block.height,
122-
)
123-
print("Heights of tips: " + str([h.height for h in tips]))
124-
print(f"Current difficulty: {difficulty}")
125-
print(f"Current VDF iterations per second: {ips:.0f}")
126-
# print("LCA data:\n", lca_block.data)
127-
print("Total iterations since genesis:", total_iters)
128-
print("")
129-
heads: List[HeaderBlock] = tips
130-
added_blocks: List[HeaderBlock] = []
131-
while len(added_blocks) < num_blocks and len(heads) > 0:
132-
heads = sorted(heads, key=lambda b: b.height, reverse=True)
133-
max_block = heads[0]
134-
if max_block not in added_blocks:
135-
added_blocks.append(max_block)
136-
heads.remove(max_block)
137-
prev: Optional[HeaderBlock] = await client.get_header(
138-
max_block.prev_header_hash
139-
)
140-
if prev is not None:
141-
heads.append(prev)
29+
parser.set_defaults(function=lambda args, parser: parser.print_help())
30+
return parser
14231

143-
latest_blocks_labels = []
144-
for i, b in enumerate(added_blocks):
145-
latest_blocks_labels.append(
146-
f"{b.height}:{b.header_hash}"
147-
f" {'LCA' if b.header_hash == lca_block.header_hash else ''}"
148-
f" {'TIP' if b.header_hash in [h.header_hash for h in tips] else ''}"
149-
)
150-
for i in range(len(latest_blocks_labels)):
151-
if i < 2:
152-
print(latest_blocks_labels[i])
153-
elif i == 2:
154-
print(
155-
latest_blocks_labels[i],
156-
"\n",
157-
" -----",
158-
)
159-
else:
160-
print("", latest_blocks_labels[i])
161-
# if called together with other arguments, leave a blank line
162-
if args.connections:
163-
print("")
164-
if args.connections:
165-
connections = await client.get_connections()
166-
print("Connections")
167-
print(
168-
f"Type IP Ports NodeID Last Connect"
169-
f" MB Up|Dwn"
170-
)
171-
for con in connections:
172-
last_connect_tuple = struct_time(localtime(con["last_message_time"]))
173-
# last_connect = time.ctime(con['last_message_time'])
174-
last_connect = time.strftime("%b %d %T", last_connect_tuple)
175-
mb_down = con["bytes_read"] / 1024
176-
mb_up = con["bytes_written"] / 1024
177-
# print (last_connect)
178-
con_str = (
179-
f"{NodeType(con['type']).name:9} {con['peer_host']:39} "
180-
f"{con['peer_port']:5}/{con['peer_server_port']:<5}"
181-
f"{con['node_id'].hex()[:10]}... "
182-
f"{last_connect} "
183-
f"{mb_down:7.1f}|{mb_up:<7.1f}"
184-
)
185-
print(con_str)
186-
# if called together with other arguments, leave a blank line
187-
if args.state:
188-
print("")
189-
if args.exit_node:
190-
node_stop = await client.stop_node()
191-
print(node_stop, "Node stopped.")
192-
if args.add_connection:
193-
if ":" not in args.add_connection:
194-
print(
195-
"Enter a valid IP and port in the following format: 10.5.4.3:8000"
196-
)
197-
else:
198-
ip, port = (
199-
":".join(args.add_connection.split(":")[:-1]),
200-
args.add_connection.split(":")[-1],
201-
)
202-
print(f"Connecting to {ip}, {port}")
203-
try:
204-
await client.open_connection(ip, int(port))
205-
except BaseException:
206-
# TODO: catch right exception
207-
print(f"Failed to connect to {ip}:{port}")
208-
if args.remove_connection:
209-
result_txt = ""
210-
if len(args.remove_connection) != 10:
211-
result_txt = "Invalid NodeID"
212-
else:
213-
connections = await client.get_connections()
214-
for con in connections:
215-
if args.remove_connection == con["node_id"].hex()[:10]:
216-
print(
217-
"Attempting to disconnect", "NodeID", args.remove_connection
218-
)
219-
try:
220-
await client.close_connection(con["node_id"])
221-
except BaseException:
222-
result_txt = (
223-
f"Failed to disconnect NodeID {args.remove_connection}"
224-
)
225-
else:
226-
result_txt = f"NodeID {args.remove_connection}... {NodeType(con['type']).name} "
227-
f"{con['peer_host']} disconnected."
228-
elif result_txt == "":
229-
result_txt = f"NodeID {args.remove_connection}... not found."
230-
print(result_txt)
231-
elif args.block_header_hash != "":
232-
block = await client.get_block(hexstr_to_bytes(args.block_header_hash))
233-
# print(dir(block))
234-
if block is not None:
235-
print("Block header:")
236-
print(block.header)
237-
block_time = struct_time(localtime(block.header.data.timestamp))
238-
print("Block time:", time.strftime("%a %b %d %Y %T %Z", block_time))
239-
else:
240-
print("Block hash", args.block_header_hash, "not found.")
24132

242-
except Exception as e:
243-
if isinstance(e, aiohttp.client_exceptions.ClientConnectorError):
244-
print(f"Connection error. Check if full node is running at {args.rpc_port}")
245-
else:
246-
print(f"Exception {e}")
33+
def chia(args, parser):
34+
# a hack to generate the config.yaml file if missing
35+
load_config("config.yaml")
24736

248-
client.close()
249-
await client.await_closed()
37+
return args.function(args, parser)
25038

25139

25240
def main():
253-
asyncio.run(async_main())
41+
parser = create_parser()
42+
args = parser.parse_args()
43+
return chia(args, parser)
25444

25545

25646
if __name__ == "__main__":

0 commit comments

Comments
 (0)