diff --git a/neo/Prompt/Commands/Search.py b/neo/Prompt/Commands/Search.py index 8c23e8aeb..1b2433385 100644 --- a/neo/Prompt/Commands/Search.py +++ b/neo/Prompt/Commands/Search.py @@ -23,7 +23,7 @@ def execute(self, arguments): item = get_arg(arguments) if not item: - print("run `%s help` to see supported queries" % CommandSearch().command_desc().command) + print("run `%s help` to see supported queries" % self.command_desc().command) return try: diff --git a/neo/Prompt/Commands/Show.py b/neo/Prompt/Commands/Show.py new file mode 100644 index 000000000..b3214ad1f --- /dev/null +++ b/neo/Prompt/Commands/Show.py @@ -0,0 +1,169 @@ +import os +import psutil +import datetime +from neo.Prompt.CommandBase import CommandBase, CommandDesc, ParameterDesc +from neo.Prompt.PromptData import PromptData +from neo.Prompt.Utils import get_arg +from neo.Core.Blockchain import Blockchain +from neocore.UInt256 import UInt256 +from neo.IO.MemoryStream import StreamManager +from neo.Network.NodeLeader import NodeLeader +from neo.Implementations.Notifications.LevelDB.NotificationDB import NotificationDB +from neo.logging import log_manager +import json + + +logger = log_manager.getLogger() + + +class CommandShow(CommandBase): + def __init__(self): + super().__init__() + + self.register_sub_command(CommandShowBlock()) + self.register_sub_command(CommandShowHeader()) + self.register_sub_command(CommandShowTx()) + self.register_sub_command(CommandShowMem()) + self.register_sub_command(CommandShowNodes(), ['node']) + + def command_desc(self): + return CommandDesc('show', 'show useful data') + + def execute(self, arguments): + item = get_arg(arguments) + + if not item: + print("run `%s help` to see supported queries" % self.command_desc().command) + return + + try: + return self.execute_sub_command(item, arguments[1:]) + except KeyError: + print(f"{item} is an invalid parameter") + return + + +class CommandShowBlock(CommandBase): + def __init__(self): + super().__init__() + + def execute(self, arguments): + item = get_arg(arguments) + txarg = get_arg(arguments, 1) + if item is not None: + block = Blockchain.Default().GetBlock(item) + + if block is not None: + block.LoadTransactions() + + if txarg and 'tx' in txarg: + txs = [] + for tx in block.FullTransactions: + print(json.dumps(tx.ToJson(), indent=4)) + txs.append(tx.ToJson()) + return txs + + print(json.dumps(block.ToJson(), indent=4)) + return block.ToJson() + + else: + print("Could not locate block %s" % item) + return + else: + print("please specify a block") + return + + def command_desc(self): + p1 = ParameterDesc('index/hash', 'the index or scripthash of the block') + p2 = ParameterDesc('tx', 'arg to only show block transactions', optional=True) + return CommandDesc('block', 'show a specified block', [p1, p2]) + + +class CommandShowHeader(CommandBase): + def __init__(self): + super().__init__() + + def execute(self, arguments): + item = get_arg(arguments) + if item is not None: + header = Blockchain.Default().GetHeaderBy(item) + if header is not None: + print(json.dumps(header.ToJson(), indent=4)) + return header.ToJson() + else: + print("Could not locate header %s\n" % item) + return + else: + print("Please specify a header") + return + + def command_desc(self): + p1 = ParameterDesc('index/hash', 'the index or scripthash of the block header') + return CommandDesc('header', 'show the header of a specified block', [p1]) + + +class CommandShowTx(CommandBase): + def __init__(self): + super().__init__() + + def execute(self, arguments): + if len(arguments): + try: + txid = UInt256.ParseString(get_arg(arguments)) + tx, height = Blockchain.Default().GetTransaction(txid) + if height > -1: + jsn = tx.ToJson() + jsn['height'] = height + jsn['unspents'] = [uns.ToJson(tx.outputs.index(uns)) for uns in + Blockchain.Default().GetAllUnspent(txid)] + print(json.dumps(jsn, indent=4)) + return jsn + else: + print(f"Could not find transaction for hash {txid}") + return + except Exception: + print("Could not find transaction from args: %s" % arguments) + return + else: + print("Please specify a TX hash") + return + + def command_desc(self): + p1 = ParameterDesc('hash', 'the scripthash of the transaction') + return CommandDesc('tx', 'show a specified transaction', [p1]) + + +class CommandShowMem(CommandBase): + def __init__(self): + super().__init__() + + def execute(self, arguments=None): + process = psutil.Process(os.getpid()) + total = process.memory_info().rss + totalmb = total / (1024 * 1024) + out = "Total: %s MB\n" % totalmb + out += "Total buffers: %s\n" % StreamManager.TotalBuffers() + print(out) + return out + + def command_desc(self): + return CommandDesc('mem', 'show memory in use and number of buffers') + + +class CommandShowNodes(CommandBase): + def __init__(self): + super().__init__() + + def execute(self, arguments=None): + if len(NodeLeader.Instance().Peers) > 0: + out = "Total Connected: %s\n" % len(NodeLeader.Instance().Peers) + for peer in NodeLeader.Instance().Peers: + out += "Peer %s - IO: %s\n" % (peer.Name(), peer.IOStats()) + print(out) + return out + else: + print("Not connected yet\n") + return + + def command_desc(self): + return CommandDesc('nodes', 'show connected peers') diff --git a/neo/Prompt/Commands/tests/test_show_commands.py b/neo/Prompt/Commands/tests/test_show_commands.py new file mode 100644 index 000000000..a297abc0c --- /dev/null +++ b/neo/Prompt/Commands/tests/test_show_commands.py @@ -0,0 +1,158 @@ +import os +from neo.Settings import settings +from neo.Utils.BlockchainFixtureTestCase import BlockchainFixtureTestCase +from neo.Prompt.Commands.Show import CommandShow +from neo.Prompt.Commands.Wallet import CommandWallet +from neo.Prompt.PromptData import PromptData +from neo.bin.prompt import PromptInterface +from copy import deepcopy +from neo.Network.NodeLeader import NodeLeader, NeoNode +from neo.Core.Blockchain import Blockchain +from neo.Implementations.Wallets.peewee.UserWallet import UserWallet +from mock import patch + + +class CommandShowTestCase(BlockchainFixtureTestCase): + + @classmethod + def leveldb_testpath(self): + return os.path.join(settings.DATA_DIR_PATH, 'fixtures/test_chain') + + @classmethod + def tearDown(cls): + PromptData.Prompt = None + PromptData.Wallet = None + + def test_show(self): + # with no subcommand + res = CommandShow().execute(None) + self.assertFalse(res) + + # with invalid command + args = ['badcommand'] + res = CommandShow().execute(args) + self.assertFalse(res) + + def test_show_block(self): + # test no block input + args = ['block'] + res = CommandShow().execute(args) + self.assertFalse(res) + + # show good block by index + args = ['block', '9'] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(res['index'], 9) + self.assertIn('tx', res) + + # show good block by hash + args = ['block', "0x7c5b4c8a70336bf68e8679be7c9a2a15f85c0f6d0e14389019dcc3edfab2bb4b"] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(res['index'], 9) + self.assertIn('tx', res) + + # show the block's transactions only + args = ['block', '9', "tx"] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(len(res), 2) + self.assertEqual(res[0]['type'], "MinerTransaction") + self.assertEqual(res[1]['type'], "ContractTransaction") + + # request bad block + args = ['block', 'blah'] + res = CommandShow().execute(args) + self.assertFalse(res) + + def test_show_header(self): + # test no header input + args = ['header'] + res = CommandShow().execute(args) + self.assertFalse(res) + + # show good header by index + args = ['header', '9'] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(res['index'], 9) + self.assertNotIn('tx', res) + + # show good header by hash + args = ['header', "0x7c5b4c8a70336bf68e8679be7c9a2a15f85c0f6d0e14389019dcc3edfab2bb4b"] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(res['index'], 9) + self.assertNotIn('tx', res) + + # request bad header + args = ['header', 'blah'] + res = CommandShow().execute(args) + self.assertFalse(res) + + def test_show_tx(self): + # test no tx input + args = ['tx'] + res = CommandShow().execute(args) + self.assertFalse(res) + + # show good tx + txid = '0x83df8bd085fcb60b2789f7d0a9f876e5f3908567f7877fcba835e899b9dea0b5' + args = ['tx', txid] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertEqual(res['txid'], txid) + self.assertIn('height', res) + self.assertIn('unspents', res) + + # query a bad tx + args = ['tx', '0x83df8bd085fcb60b2789f7d0a9f876e5f3908567f7877fcba835e899b9dea0b6'] + res = CommandShow().execute(args) + self.assertFalse(res) + + # query with bad args + args = ['tx', 'blah'] + res = CommandShow().execute(args) + self.assertFalse(res) + + def test_show_mem(self): + args = ['mem'] + res = CommandShow().execute(args) + self.assertTrue(res) + + def test_show_nodes(self): + # query nodes with no NodeLeader.Instance() + with patch('neo.Network.NodeLeader.NodeLeader.Instance'): + args = ['nodes'] + res = CommandShow().execute(args) + self.assertFalse(res) + + # query nodes with connected peers + # first make sure we have a predictable state + leader = NodeLeader.Instance() + old_leader = deepcopy(leader) + leader.ADDRS = ["127.0.0.1:20333", "127.0.0.2:20334"] + leader.DEAD_ADDRS = ["127.0.0.1:20335"] + test_node = NeoNode() + test_node.host = "127.0.0.1" + test_node.port = 20333 + leader.Peers = [test_node] + + # now show nodes + with patch('neo.Network.NeoNode.NeoNode.Name', return_value="test name"): + args = ['nodes'] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertIn('Total Connected: 1', res) + self.assertIn('Peer test name - IO: 0.0 MB in / 0.0 MB out', res) + + # now use "node" + args = ['node'] + res = CommandShow().execute(args) + self.assertTrue(res) + self.assertIn('Total Connected: 1', res) + self.assertIn('Peer test name - IO: 0.0 MB in / 0.0 MB out', res) + + # restore whatever state the instance was in + NodeLeader._LEAD = old_leader diff --git a/neo/bin/prompt.py b/neo/bin/prompt.py index 6cbf1f489..e8d0290a9 100755 --- a/neo/bin/prompt.py +++ b/neo/bin/prompt.py @@ -16,6 +16,7 @@ from neo.Implementations.Notifications.LevelDB.NotificationDB import NotificationDB from neo.Network.NodeLeader import NodeLeader from neo.Prompt.Commands.Wallet import CommandWallet +from neo.Prompt.Commands.Show import CommandShow from neo.Prompt.Commands.Search import CommandSearch from neo.Prompt.PromptData import PromptData from neo.Prompt.InputParser import InputParser @@ -71,7 +72,7 @@ class PromptInterface: _known_things = [] _commands = [ - CommandWallet(), CommandSearch() + CommandWallet(), CommandShow(), CommandSearch() ] _command_descs = [desc for c in _commands for desc in c.command_descs_with_sub_commands()]