Skip to content
This repository was archived by the owner on Nov 15, 2021. It is now read-only.

Conversation

@ixje
Copy link
Member

@ixje ixje commented Apr 8, 2019

This PR ripped the 3rd party event-driven framework twisted out of neo-python and replaced it with the standard library asyncio. It also introduces new network code.

neo-python doesn't have 100% test coverage and this PR touches pretty much every component, therefore I'm looking for help with general testing of this branch. This can be anything from running the RPC server, to general interactions like opening/closing/rebuilding wallets, sending assets, searching contracts. Whatever feature is in there should have no regression and function as expected. If it shows unexpected behaviour (read the note below first) then please drop a comment here, also for any errors you think shouldn't be there/printed .

Important notes

  1. requires python 3.7
  2. This branch incorporates Fix sys_fee calculation in block persist #905 and will give a warning that the database format has changed. Using np-bootstrap as suggested will not work as the bootstrap files have not yet been updated.
  3. The network code in this branch relies on a new network feature introduced in last weeks release of neo-cli -> v2.10.1. Therefore when using this in a private-net environment make sure you run it against nodes with neo-cli v2.10.1. Upgrade neo-privatenet using:
docker pull cityofzion/neo-privatenet:2.10.1

neo-local is not yet updated afaik.

To elaborate why this is important: the >= 2.9.x <= 2.10.0 series of neo-cli started tracking data sent to a peer and limit requesting the same data to only once per session. In order to keep up to date with the best height of a connected node in those series we have 2 options

  1. reconnect and extract the height information during the handshake
  2. request the last block, read the height from there and cache this block until you can finally process it. When syncing e.g. mainnet this means you can end up caching thousands of blocks in memory because you can't process them until in sync.

I found neither approach acceptable and convinced NEO to add a new network command that allows requesting the best height of a peer without disconnecting. Without going into too much detail, the new network code uses this information to avoid requesting data from a node that doesn't have the data in the first place. The impact for now is that neo-cli nodes < 2.10.1 basically cannot stay in sync. It will be a transition period that we'll have to live with.

ixje added 3 commits April 3, 2019 19:18
- add hack to work around max 1 request per hash session limit causing out of sync
- more logic to shutdown cleanly (+9 squashed commits)
Squashed commits:
[59c5916] - move mempool out of nodemanager
- add relay cache + logic
- add getdata support in node for relay cache items only
[1d081e9] add helper logic to avoid trying to persist blocks multiple times and keeping wallet height in check
[c20cd6e] fix node.relay() , remove property for size in message
[383ad47] - Add new ping payload support
- resolve send_addr issue
- cleanup unused functions, add missing typing
[8bed164] clean up prints to use logger, remove obsolete code
[9791737] Update db schema, ensure setting up an asyncio loop before initialising a DB, don't manually stop loop after run complete
[1499f91] - update requirements
- fix /examples/
- fix `set maxpeers` not adding new CLI clients
- fix exceeding max nodes connection count
- add IPfiltering (blacklist/whitelist)
- fix show nodes extra flags not working with 0 connected nodes
- add code for more graceful shutdown preventing stack traces and db corruption
- update trouble shooting section after OSX update when `scrypt` module fails.
[2743930] cleanup
[ee2f4c6] New network code + move from twisted to asyncio

cleanup

- update requirements
- fix /examples/
- fix `set maxpeers` not adding new CLI clients
- fix exceeding max nodes connection count
- add IPfiltering (blacklist/whitelist)
- fix show nodes extra flags not working with 0 connected nodes
- add code for more graceful shutdown preventing stack traces and db corruption
- update trouble shooting section after OSX update when `scrypt` module fails.

Update db schema, ensure setting up an asyncio loop before initialising a DB, don't manually stop loop after run complete

clean up prints to use logger, remove obsolete code

- Add new ping payload support
- resolve send_addr issue
- cleanup unused functions, add missing typing

fix node.relay() , remove property for size in message

add helper logic to avoid trying to persist blocks multiple times and keeping wallet height in check

- move mempool out of nodemanager
- add relay cache + logic
- add getdata support in node for relay cache items only

- add hack to work around max 1 request per hash session limit causing out of sync
- more logic to shutdown cleanly
- cleanup logger stuff
- ensure we can't persist blocks that are out of order
- remove tcp session hack
@ixje
Copy link
Member Author

ixje commented Apr 8, 2019

@jseagrave21 @ThomasLobker 🙏

@jseagrave21
Copy link
Contributor

Awesome! :) I will absolutely start reviewing/testing this. As you know, this will take me some time.

@coveralls
Copy link

coveralls commented Apr 8, 2019

Coverage Status

Coverage decreased (-2.5%) to 84.235% when pulling 7469e3b on ixje:asyncio into 3b8bfb6 on CityOfZion:development.

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

NOTE: I am going to enter check boxes next to my recommendations in case you would like to use them as a reference. A checkbox in my mind means the comment/recommendations has been addressed either with a fix or some other justification.

  • When I entered np-api-server -h, I received
usage: np-api-server [-h]
                     (--mainnet | --testnet | --privnet | --coznet | --config CONFIG)
                     [--port-rpc PORT_RPC] [--port-rest PORT_REST]
                     [--logfile LOGFILE] [--syslog] [--syslog-local [0-7]]
                     [--disable-stderr] [--datadir DATADIR]
                     [--minpeers MINPEERS] [--maxpeers MAXPEERS]
                     [--wallet WALLET] [--host HOST]

optional arguments:
  -h, --help            show this help message and exit
  --datadir DATADIR     Absolute path to use for database directories
  --minpeers MINPEERS   Min peers to use for P2P Joining
  --maxpeers MAXPEERS   Max peers to use for P2P Joining
  --wallet WALLET       Open wallet. Will allow you to use methods that
                        require an open wallet
  --host HOST           Hostname ( for example 127.0.0.1)

Network options:
  --mainnet             Use MainNet
  --testnet             Use TestNet
  --privnet             Use PrivNet
  --coznet              Use CozNet
  --config CONFIG       Use a specific config file

Mode(s):
  --port-rpc PORT_RPC   port to use for the json-rpc api (eg. 10332)
  --port-rest PORT_REST
                        port to use for the rest api (eg. 80)

Logging options:
  --logfile LOGFILE     Logfile
  --syslog              Log to syslog instead of to log file ('user' is the
                        default facility)
  --syslog-local [0-7]  Log to a local syslog facility instead of 'user'.
                        Value must be between 0 and 7 (e.g. 0 for 'local0').
  --disable-stderr      Disable stderr logger
[I 190408 21:41:06 api_server:314] Shutting down...
Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 312, in main
    loop.run_forever()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 523, in run_forever
    self._run_once()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1758, in _run_once
    handle._run()
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 124, in setup_and_start
    args = parser.parse_args()
  File "/usr/lib/python3.7/argparse.py", line 1758, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1996, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1936, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1047, in __call__
    parser.exit()
  File "/usr/lib/python3.7/argparse.py", line 2497, in exit
    _sys.exit(status)
SystemExit: 0

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/venv/bin/np-api-server", line 11, in <module>
    load_entry_point('neo-python', 'console_scripts', 'np-api-server')()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 316, in main
    loop.run_until_complete(p2p.shutdown())
  File "/usr/lib/python3.7/asyncio/base_events.py", line 568, in run_until_complete
    return future.result()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/p2pservice.py", line 34, in shutdown
    await self.syncmgr.shutdown()
AttributeError: 'NoneType' object has no attribute 'shutdown'
Task exception was never retrieved
future: <Task finished coro=<setup_and_start() done, defined at /mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py:80> exception=SystemExit(0)>
Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 312, in main
    loop.run_forever()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 523, in run_forever
    self._run_once()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1758, in _run_once
    handle._run()
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 124, in setup_and_start
    args = parser.parse_args()
  File "/usr/lib/python3.7/argparse.py", line 1758, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.7/argparse.py", line 1996, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.7/argparse.py", line 1936, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1864, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.7/argparse.py", line 1047, in __call__
    parser.exit()
  File "/usr/lib/python3.7/argparse.py", line 2497, in exit
    _sys.exit(status)
SystemExit: 0
  • fix np-api-server -h

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

Running testnet api_server:

  • I see [D 190408 22:01:27 syncmanager:94] Requested headers starting at 1 from node 140521435119912
  • I don't think node 140521435119912 is very descriptive. I would prefer to see the IP address or something more informative. These titles are referred to frequently and aren't intuitive to me.
  • minor: I see [D 190408 22:03:12 syncmanager:184] header timeout limit exceeded by 0.0704951286315918s for node 140521435121984
  • I recommend trimming the displayed exceeded times to two decimals.

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

Whenever I query the server, the response is immediate even while syncing! 💪

However, for the np-api-server interface, I am used to seeing each request, which I appreciated, especially when I thought I was being spammed. I can easily see the "200" or "404" response e.g.

[I 190408 22:27:41 _observer:131] "165.227.220.142" - - [09/Apr/2019:02:27:40 +0000] "POST / HTTP/1.1" 200 81 "-" "Python/3.6 aiohttp/3.5.4"

Additionally, I have edited JsonRpcApi.py so that I see each method and param that is included in a request. This information is displayed but not the normal request data, so if I hadn't made that edit, then I would have no idea my server was being queried.

  • I recommend reintroducing a display of each server request

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

When shutting down the server, I see

^C[I 190408 22:35:17 api_server:314] Shutting down...
Shutting down sync manager...Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 312, in main
    loop.run_forever()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 523, in run_forever
    self._run_once()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1758, in _run_once
    handle._run()
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 301, in system_exit
    raise SystemExit
SystemExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/venv/bin/np-api-server", line 11, in <module>
    load_entry_point('neo-python', 'console_scripts', 'np-api-server')()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 316, in main
    loop.run_until_complete(p2p.shutdown())
  File "/usr/lib/python3.7/asyncio/base_events.py", line 568, in run_until_complete
    return future.result()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/p2pservice.py", line 34, in shutdown
    await self.syncmgr.shutdown()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/syncmanager.py", line 57, in shutdown
    await self.service_task
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/syncmanager.py", line 63, in run_service
    await self.check_timeout()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/syncmanager.py", line 169, in check_timeout
    await asyncio.gather(task1, task2)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/syncmanager.py", line 195, in check_header_timeout
    self.nodemgr.add_node_timeout_count(flight_info.node_id)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/nodemanager.py", line 201, in add_node_timeout_count
    self.replace_node(node)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/nodemanager.py", line 179, in replace_node
    wait_for(node.disconnect())
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/common/__init__.py", line 14, in wait_for
    return loop.run_until_complete(coro)
  File "/usr/lib/python3.7/asyncio/base_events.py", line 568, in run_until_complete
    return future.result()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/node.py", line 92, in disconnect
    await t
RuntimeError: Task <Task pending coro=<NeoNode.disconnect() running at /mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/node.py:92> cb=[_run_until_complete_cb() at /usr/lib/python3.7/asyncio/base_events.py:150]> got Future <Task pending coro=<NeoNode.run() running at /mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/node.py:165> wait_for=<_GatheringFuture pending cb=[<TaskWakeupMethWrapper object at 0x7fcdb0eb7228>()]>> attached to a different loop
Task was destroyed but it is pending!
task: <Task pending coro=<custom_background_code() running at /mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py:77> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fcdb0f08258>()]>>
  • fix api_server shutdown

@jseagrave21
Copy link
Contributor

It seems like after awhile the server loses sync and isn't able to regain it.

  • I recommend reintroducing a BlockheightCheck equivalent to prevent the server from losing sync

@ixje
Copy link
Member Author

ixje commented Apr 9, 2019

Thanks, really good input!

  • fixed np-api-server -h
  • limited time exceeded precision to 2 decimals
  • changed node identifier to print using base62 like TinyURL does to make it more readable. Not the best yet, but IP also didn't seem right (too big)

not or not yet successfully done

  • show api-server request. I'm not sure I'm following 100%. You already edited the RpcServer to show the information you want? If so, can you paste the edits you'd like to see
  • server shutdown error; this seems to be a really hard thing todo in an asynchronous environment as there is essentially no mechanism for 100% success (More info). I already try to manually determine the order and gracefully shutdown and finally finish with a catch all, but even that still doesn't catch all it seems :(
    I've added another 2 checks/edits that hopefully catch them 🤞
  • the out of sync is described in point 3 of the "important notes" section. There actually is some logic that replaces nodes, but I'll consider making it more aggressive

@jseagrave21
Copy link
Contributor

  • show api-server request. I'm not sure I'm following 100%. You already edited the RpcServer to show the information you want? If so, can you paste the edits you'd like to see

My edit is very basic because I like to see exactly what the requests are, so I put this line

print(f"Method: {method} | Params: {params})

at the beginning of json_rpc_method_handler.

What I am asking for is to reintroduce the code that produces lines like

[I 190408 22:27:41 _observer:131] "165.227.220.142" - - [09/Apr/2019:02:27:40 +0000] "POST / HTTP/1.1" 200 81 "-" "Python/3.6 aiohttp/3.5.4"

for each request. I can't seem to find _observer so I don't know where to look for the source code.

@ixje
Copy link
Member Author

ixje commented Apr 9, 2019

ah I see. The _observer is something from the old Twisted or Klein package. I did a quick search for aiohttp but haven't found a clear solution yet, only an issue on their repo that it is not clear how to set it up (ref).

@jseagrave21
Copy link
Contributor

@ixje I checked my requests recommendation as complete based on 7469e3b. Looks great!

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

  • server shutdown error; this seems to be a really hard thing todo in an asynchronous environment as there is essentially no mechanism for 100% success (More info). I already try to manually determine the order and gracefully shutdown and finally finish with a catch all, but even that still doesn't catch all it seems :(
    I've added another 2 checks/edits that hopefully catch them

Your update did not resolve the issue; however, it looks like a different unhandled exception now:

^C[I 190409 15:31:55 api_server:314] Shutting down...
Shutting down sync manager...DONE
Shutting down node manager...DONE
Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 312, in main
    loop.run_forever()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 523, in run_forever
    self._run_once()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 1758, in _run_once
    handle._run()
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 301, in system_exit
    raise SystemExit
SystemExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/c/users/seagrave/neo/asyncio/venv/bin/np-api-server", line 11, in <module>
    load_entry_point('neo-python', 'console_scripts', 'np-api-server')()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 317, in main
    loop.run_until_complete(shutdown())
  File "/usr/lib/python3.7/asyncio/base_events.py", line 568, in run_until_complete
    return future.result()
  File "/mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py", line 297, in shutdown
    await task
  File "/mnt/c/users/seagrave/neo/asyncio/neo/Network/neonetwork/network/nodemanager.py", line 332, in _connect_to_node
    await asyncio.wait_for(connect_coro, timeout)
  File "/usr/lib/python3.7/asyncio/tasks.py", line 412, in wait_for
    return fut.result()
  File "/usr/lib/python3.7/asyncio/base_events.py", line 943, in create_connection
    raise exceptions[0]
  File "/usr/lib/python3.7/asyncio/base_events.py", line 930, in create_connection
    await self.sock_connect(sock, address)
  File "/usr/lib/python3.7/asyncio/selector_events.py", line 475, in sock_connect
    return await fut
  File "/usr/lib/python3.7/asyncio/selector_events.py", line 505, in _sock_connect_cb
    raise OSError(err, f'Connect call failed {address}')
ConnectionRefusedError: [Errno 111] Connect call failed ('37.59.32.210', 20333)
Task was destroyed but it is pending!
task: <Task pending coro=<custom_background_code() running at /mnt/c/users/seagrave/neo/asyncio/neo/bin/api_server.py:77> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f87d07a3648>()]>>

I was able to get it to work with by suppressing every Exception. So in api_server,

async def shutdown():
    # cleanup any remaining tasks
    for task in asyncio.Task.all_tasks():
        with suppress(asyncio.CancelledError):
            with suppress(Exception):
                task.cancel()
                await task

and in p2pservice,

async def shutdown(self):
        with suppress(asyncio.CancelledError):
            with suppress(Exception):
                if self.syncmgr:
                    await self.syncmgr.shutdown()

On a related note, did you happen to see this for graceful shutdowns? Not sure if it helps. I thought the handle_exception function might be useful for us.
https://www.roguelynn.com/words/asyncio-we-did-it-wrong-pt-2/

@jseagrave21
Copy link
Contributor

  • changed node identifier to print using base62 like TinyURL does to make it more readable. Not the best yet, but IP also didn't seem right (too big)

imo the useful information inherent with an IP address overshadows its size. I am still in favor of just using the IP address.

Also, it appears that your rename update did not take everywhere:

[D 190409 15:49:38 syncmanager:184] header timeout limit exceeded by 0.03s for node 139884723654952

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 9, 2019

the out of sync is described in point 3 of the "important notes" section. There actually is some logic that replaces nodes, but I'll consider making it more aggressive

I don't think I was originally seeing all the debug statements and my original review of the code was only cursory (also, it is a lot of changes). I can see now this loop, which is interesting:

[I 190409 15:58:48 api_server:81] [TestNet] Block 593999 / 595999
[D 190409 15:59:03 node:170] Updating node DIKrcRLW height from 2521691 to 2521692
[D 190409 15:59:03 nodemanager:107] Connected node count 20
[I 190409 15:59:03 api_server:81] [TestNet] Block 593999 / 595999
[D 190409 15:59:18 nodemanager:107] Connected node count 20
[I 190409 15:59:20 api_server:81] [TestNet] Block 593999 / 595999
[D 190409 15:59:20 node:170] Updating node DIKrcRLW height from 2521692 to 2521693
[D 190409 15:59:35 nodemanager:107] Connected node count 20
[I 190409 15:59:35 api_server:81] [TestNet] Block 593999 / 595999
[D 190409 15:59:40 node:170] Updating node DIKrcRLW height from 2521693 to 2521694
[D 190409 15:59:50 nodemanager:107] Connected node count 20
[I 190409 15:59:50 api_server:81] [TestNet] Block 593999 / 595999
[D 190409 16:00:00 node:170] Updating node DIKrcRLW height from 2521694 to 2521695
[D 190409 16:00:05 nodemanager:107] Connected node count 20

I think that DIKrcRLW is the 2.10.1 node because I can see realtime updating of its height which is great. Also, there are still 20 connected nodes. However, syncing has stalled. 🤷‍♂️

@jseagrave21
Copy link
Contributor

The asyncio_server is ~an order of magnitude more efficient than the current server (comparing CPU usage)! 💪

Copy link
Contributor

@jseagrave21 jseagrave21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are a few items I found

except SystemExit:
logger.info("Shutting down...")
site = main_task.result()
loop.run_until_complete(site.stop())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will result in

AttributeError: 'NoneType' object has no attribute 'stop'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reproduce this? If so what are the steps? It doesn't seem to happen here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I realized my mistake. I actually added this onto the code for np-api-server so that is probably why it didn't work. Sorry about that.

Copy link
Contributor

@jseagrave21 jseagrave21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here fa0cfb9#diff-8aa8dec2a1f7f08ba100b97063d796eaR33
you meant with suppress((asyncio.CancelledError, Exception)):

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 18, 2019

@ixje please see the latest commit in https://github.com/ixje/neo-python/pull/5 which fixes the shutdown issue. Please note, I didn't push to a separate PR because the shutdown improvements also affected restart() which only exists in my asyncio branch. Of course, if you would like me to separate out my global fixes for a separate PR (or something else), let me know.

Copy link
Contributor

@jseagrave21 jseagrave21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these notifications are better served as debug statements because they aren't cuing any action from the user.

self.connection_lost(ConnectionError)
self.disconnect()
except asyncio.CancelledError:
print("task cancelled, closing connection")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should be debug statements

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding these print statements (and those below) . Yes, eventually they shouldn't be there at all. I think even including them with logger.debug might generate too much noise. I'll removing them as a TODO but want to keep them for now because I think they're useful at this stage

self.connection_lost(ConnectionResetError)
self.disconnect()
except ConnectionError:
print("connection error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

self._stream_writer.write(message.to_array())
await self._stream_writer.drain()
except ConnectionResetError:
print("connection reset")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

self.disconnect()
except Exception as e:
self.connection_lost()
print(f"***** woah what happened here?! {str(e)}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

@jseagrave21
Copy link
Contributor

I got this deprecation warning when testing on a different computer and downloading the test fixture:

DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  • I recommend addressing it in this PR.

Copy link
Contributor

@jseagrave21 jseagrave21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some comments from my second pass through, now having a slightly better understanding of asyncio.

passwd = prompt("[password]> ", is_password=True)
except KeyboardInterrupt:
print("Wallet opening cancelled")
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why delete this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, added it back. I was probably trying out shutdown approaches and this might have been a test that failed with one of the approaches


relayed = NodeLeader.Instance().Relay(contract_tx)
nodemgr = NodeManager()
# this blocks, consider moving this wallet function to async instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this instance of relaying somehow different than the others? I am just wondering about the comment and this is the first time I've seen it.

Copy link
Contributor

@jseagrave21 jseagrave21 Apr 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After finishing reading through the changes, I saw one more similar comment as a "TODO". Is the relay func something that should be improved before release?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has todo with slowly migrating to asyncio. relay in

relayed = nodemgr.relay(contract_tx)

should ideally be non-blocking. But because we're calling it from a function that is not defined as async def we cannot turn def relay() into async def relay and await it. Therefore relay looks like

def relay(self, inventory) -> bool:
if type(inventory) is OrigTransaction or issubclass(type(inventory), OrigTransaction):
success = self.mempool.add_transaction(inventory)
if not success:
return False
# TODO: should we keep the tx in the mempool if relaying failed? There is currently no mechanism that retries sending failed tx's
return wait_for(self.relay_directly(inventory))

where wait_for is a helper function that can call "awaitable" code from non async code.
It does not have to be improved before release. There is too much code still to port to async. That's why this wait_for exists to help in migrating over time. The advice is to only use asyncio when starting a new project, but there are cases like this where you'll have to slowly migrate. I got this wait_for trick from a guy at Instagram where they're also slowly migrating their code base to asyncio.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Okay, sounds good.

claim_tx, relayed = ClaimGas(wallet)
self.assertEqual(claim_tx, None)
self.assertFalse(relayed)
self.assertIn("Claim transaction cancelled", mock_print.getvalue())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this test deleted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing same reason as the wallet cancellation. Added it back.

self.assertEqual(leader.Peers[0].address, test_node.address)
self.assertTrue(mock_disconnect.called)
self.assertIn(f"Maxpeers set to {settings.CONNECTED_PEER_MAX}", mock_print.getvalue())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that the maxpeers command was refactored but the logic added in #878 is still there so why is this test removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I noticed that there is now a minpeers attribute but no associated config minpeers or tests. Is this something I could/should add?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This asyncio branch no longer has a NodeLeader class so this logic can't work and has been refactored (wait for new asyncio PR)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I am still a little confused. Does this mean that you already have the new code but are just waiting to commit in a separate PR?

nodemgr.reset_for_test()

def test_get_version(self):
# TODO: what's the nonce? on testnet live server response it's always 771199013
Copy link
Contributor

@jseagrave21 jseagrave21 Apr 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we assert the nonce value in the test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

@ixje
Copy link
Member Author

ixje commented Apr 25, 2019

I finally caught up with the 20+ commits/PR's they did on the neo-vm project. I'm going to close this PR soon and start a new one because I think it's unlikely that others will join a PR with ~40 comments.

DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working

Can you describe the steps that produce this error? I searched the code base for any importing from collections and couldn't find anything related to ABC's (Abstract Base Class)

@jseagrave21
Copy link
Contributor

jseagrave21 commented Apr 25, 2019

Okay, sounds good.

Can you describe the steps that produce this error? I searched the code base for any importing from collections and couldn't find anything related to ABC's (Abstract Base Class)

I received this warning twice while testing on a new computer which didn't have the test fixtures downloaded. The warning was received each time a test fixture was downloaded. I am not sure if that is related to the warning but that was when it occured.

-edit-

@ixje
It was specifically when downloading the fixture for the neo-boa test:

export NEOPYTHON_UNITTEST=1; python3 -m unittest discover boa_test
[I 190425 17:16:02 BlockchainFixtureTestCase:56] downloading fixture block database from https://s3.us-east-2.amazonaws.com/cityofzion/fixtures/fixtures_v8.tar.gz. this may take a while
/mnt/c/users/jseag/neo/asyncio_2/venv/share/python-wheels/requests-2.18.4-py2.py3-none-any.whl/requests/models.py:177: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working

@ixje
Copy link
Member Author

ixje commented Apr 25, 2019

closing in favour of #934

@ixje ixje closed this Apr 25, 2019
@ixje
Copy link
Member Author

ixje commented Apr 25, 2019

400 points seagrave

He has been doing a lot extensive testing and reviewing for this PR. On top of that he has been creating PR's directly to my 'private repo' to address certain issues. This is an ongoing effort for one of the biggest changes to neo-python up to date and the support is well appreciated.

The process will be continued in #934

@lllwvlvwlll
Copy link
Member

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants