After #11439, this PR only improves:
- test comments;
- simplicity by removing duplicate tests;
- also removes duplicate code.
Ping @jnewbery, @ryanofsky and @TheBlueMatt.
80 | + 81 | + # Subscribe to all available topics. 82 | + self.subscribe("hashblock") 83 | + self.subscribe("hashtx") 84 | + self.subscribe("rawblock") 85 | + self.subscribe("rawtx")
I found 'subscribe' a bit misleading here, though it could be that I'm thinking too much.
It's not merely a standard ZeroMQ subscription; the ZMQSubscriber class created tracks sequence. As it's written, my immediate thought was 'why aren't we just doing self.subscribe("")' (pre this commit, the four setsockopt calls could have been replaced with just one).
Perhaps 'create_subscriber' or fluffing up the comment a bit.
I can rename.
31 | + self.socket.setsockopt(zmq.SUBSCRIBE, type.encode('latin-1')) 32 | + self.subscribers[type] = ZMQSubscriber(self.socket, self.address) 33 | + 34 | + def receive(self, type): 35 | + subscriber = self.subscribers[type] 36 | + topic, body, seq = subscriber.socket.recv_multipart()
The subscribers don't actually have independent sockets, perhaps just use self.socket?
I would like to leave it this way so in the future we can have more sockets.
29 | + def subscribe(self, type): 30 | + import zmq 31 | + self.socket.setsockopt(zmq.SUBSCRIBE, type.encode('latin-1')) 32 | + self.subscribers[type] = ZMQSubscriber(self.socket, self.address) 33 | + 34 | + def receive(self, type):
nit: assert_receive? My initial thought was that you're selectively receiving a type (which makes sense if different sockets, but if the sockets are all identical, you get what you're given, as this code tests)
Below feels better to read foo = self.receive(...), no need for the assert_ IMO.
but if the sockets are all identical, you get what you're given
Not really, if below you swap 2 consecutive receives the test will fail. What comes from the socket has a specific order and we must follow it when reading.
47 | @@ -24,7 +48,7 @@ def setup_nodes(self): 48 | except ImportError: 49 | raise SkipTest("python3-zmq module not available.") 50 | 51 | - # Check that bitcoin has been built with ZMQ enabled 52 | + # Check that bitcoin has been built with ZMQ enabled. 53 | config = configparser.ConfigParser() 54 | if not self.options.configfile: 55 | self.options.configfile = os.path.dirname(__file__) + "/../config.ini"
Likely a pre-existing issue, on my box this prevents python zmq_test.py running in isolation (works through test_runner because the option already exists). If you wrap it e.g. os.path.abspath(os.path.dirname(__file__)) (as test_runner.py does) it works.
It works for me 🙄
I'll check with your suggestion.
Also should use os.path.join()
Ok.
Tested ACK db3b646bac37a54cb09f121b048fcfbb6d522808, few nits above.
Thanks for the review @esotericnonsense.
22 | class ZMQTest (BitcoinTestFramework): 23 | def set_test_params(self): 24 | + self.address = "tcp://127.0.0.1:28332" 25 | + self.num_blocks = 5 26 | self.num_nodes = 2 27 | + self.subscribers = {}
I don't like adding these assignments to set_test_params.
num_blocks and subscribers can be local variables in run_test()address should be a local variable in setup_nodes() and passed into the ZMQSubscriber constructor. Conceptually, address is the address of the ZMQ subscriber, not the test framework.set_test_params() should be used for overriding the number of nodes, network topology, etc.
Ok.
67 | - ip_address = "tcp://127.0.0.1:28332" 68 | - self.zmqSubSocket.connect(ip_address) 69 | - self.extra_args = [['-zmqpubhashblock=%s' % ip_address, '-zmqpubhashtx=%s' % ip_address, 70 | - '-zmqpubrawblock=%s' % ip_address, '-zmqpubrawtx=%s' % ip_address], []] 71 | + # Initialize ZMQ context and socket. 72 | + # At the moment all messages are received in the same socket which means
Nit: remove At the moment
Ok.
24 | + self.address = "tcp://127.0.0.1:28332" 25 | + self.num_blocks = 5 26 | self.num_nodes = 2 27 | + self.subscribers = {} 28 | + 29 | + def subscribe(self, type):
As in my comment here #11439 (review), I think it makes sense for the subscribe and receive logic to be owned by ZMQSubscriber
Ok.
Concept ACK. A few nits inline
Rebased and added a commit to address comments.Will squash after reviews.
12 | @@ -13,6 +13,25 @@ 13 | hash256, 14 | ) 15 | 16 | +class ZMQSubscriber: 17 | + def __init__(self, socket, type):
type is a builtin. Perhaps notification _type is better?
One small nit, but otherwise looks great.
Tested ACK 480c4526b845f69072306a047a4718d769403cdd. Please squash commits before merge.
65 | - self.zmqSubSocket.connect(ip_address) 66 | - self.extra_args = [['-zmqpubhashblock=%s' % ip_address, '-zmqpubhashtx=%s' % ip_address, 67 | - '-zmqpubrawblock=%s' % ip_address, '-zmqpubrawtx=%s' % ip_address], []] 68 | + # Initialize ZMQ context and socket. 69 | + # All messages are received in the same socket which means 70 | + # that this test fails if the publishing order changes.
The ZMQSubscriber class makes it harder for the test to work if topics are sent in a different order.
Can you take a look at and consider squashing 3668dcdd5a7a12dea0ce5fd9a1d7d17ade0f7b75, which builds on your change, but gets rid of ZMQSubscriber, shortens the test, and lets it pass regardless of ordering of messages from different topics?
@ryanofsky Thanks for the time spent on this. For the moment we want this test to fail if the order changes, see #11439 (comment).
BTW, if the order changes it's only a matter of changing the order of the sub.receive() calls.
Thanks for the input @ryanofsky . I personally think the ZMQSubscriber class is clearer, but it was me who pushed @promag in that direction initially, so we should see what other reviewers think.
EDIT: Actually, this isn't true. I was thinking of the other unnecessarily fragile version of the test that used multiple sockets :)
Ah right, that one hangs :+1:
ACK cc9ee809ad19a63ca284d2fbc327ac1cbcee31e4. This is an improvement over the current test, though I think it should be simplified more and made less fragile by getting rid of the ZMQSubscriber class.