Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/mininode.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2010 ArtForz -- public domain half-a-node | # Copyright (c) 2010 ArtForz -- public domain half-a-node | ||||
# Copyright (c) 2012 Jeff Garzik | # Copyright (c) 2012 Jeff Garzik | ||||
# Copyright (c) 2010-2016 The Bitcoin Core developers | # Copyright (c) 2010-2016 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Bitcoin P2P network half-a-node. | """Bitcoin P2P network half-a-node. | ||||
This python code was modified from ArtForz' public domain half-a-node, as | This python code was modified from ArtForz' public domain half-a-node, as | ||||
found in the mini-node branch of http://github.com/jgarzik/pynode. | found in the mini-node branch of http://github.com/jgarzik/pynode. | ||||
NodeConn: an object which manages p2p connectivity to a bitcoin node | P2PConnection: A low-level connection object to a node's P2P interface | ||||
NodeConnCB: a base class that describes the interface for receiving | P2PInterface: A high-level interface object for communicating to a node over P2P""" | ||||
callbacks with network messages from a NodeConn | |||||
""" | |||||
import asyncore | import asyncore | ||||
from collections import defaultdict | from collections import defaultdict | ||||
from io import BytesIO | from io import BytesIO | ||||
import logging | import logging | ||||
import socket | import socket | ||||
import struct | import struct | ||||
import sys | import sys | ||||
from threading import RLock, Thread | from threading import RLock, Thread | ||||
Show All 29 Lines | |||||
MAGIC_BYTES = { | MAGIC_BYTES = { | ||||
"mainnet": b"\xe3\xe1\xf3\xe8", | "mainnet": b"\xe3\xe1\xf3\xe8", | ||||
"testnet3": b"\xf4\xe5\xf3\xf4", | "testnet3": b"\xf4\xe5\xf3\xf4", | ||||
"regtest": b"\xda\xb5\xbf\xfa", | "regtest": b"\xda\xb5\xbf\xfa", | ||||
} | } | ||||
class NodeConn(asyncore.dispatcher): | class P2PConnection(asyncore.dispatcher): | ||||
"""A low-level connection object to a node's P2P interface. | """A low-level connection object to a node's P2P interface. | ||||
This class is responsible for: | This class is responsible for: | ||||
- opening and closing the TCP connection to the node | - opening and closing the TCP connection to the node | ||||
- reading bytes from and writing bytes to the socket | - reading bytes from and writing bytes to the socket | ||||
- deserializing and serializing the P2P message header | - deserializing and serializing the P2P message header | ||||
- logging messages as they are sent and received | - logging messages as they are sent and received | ||||
This class contains no logic for handing the P2P message payloads. It must be | This class contains no logic for handing the P2P message payloads. It must be | ||||
sub-classed and the on_message() callback overridden. | sub-classed and the on_message() callback overridden.""" | ||||
TODO: rename this class P2PConnection.""" | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__(map=mininode_socket_map) | super().__init__(map=mininode_socket_map) | ||||
def peer_connect(self, dstaddr, dstport, net="regtest"): | def peer_connect(self, dstaddr, dstport, net="regtest"): | ||||
self.dstaddr = dstaddr | self.dstaddr = dstaddr | ||||
self.dstport = dstport | self.dstport = dstport | ||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | def _log_message(self, direction, msg): | ||||
log_message = "Received message from " | log_message = "Received message from " | ||||
log_message += "%s:%d: %s" % (self.dstaddr, | log_message += "%s:%d: %s" % (self.dstaddr, | ||||
self.dstport, repr(msg)[:500]) | self.dstport, repr(msg)[:500]) | ||||
if len(log_message) > 500: | if len(log_message) > 500: | ||||
log_message += "... (msg truncated)" | log_message += "... (msg truncated)" | ||||
logger.debug(log_message) | logger.debug(log_message) | ||||
class NodeConnCB(NodeConn): | class P2PInterface(P2PConnection): | ||||
"""A high-level P2P interface class for communicating with a Bitcoin Cash node. | """A high-level P2P interface class for communicating with a Bitcoin Cash node. | ||||
This class provides high-level callbacks for processing P2P message | This class provides high-level callbacks for processing P2P message | ||||
payloads, as well as convenience methods for interacting with the | payloads, as well as convenience methods for interacting with the | ||||
node over P2P. | node over P2P. | ||||
Individual testcases should subclass this and override the on_* methods | Individual testcases should subclass this and override the on_* methods | ||||
if they want to alter message handling behaviour. | if they want to alter message handling behaviour.""" | ||||
TODO: rename this class P2PInterface""" | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
# Track number of messages of each type received and the most recent | # Track number of messages of each type received and the most recent | ||||
# message of each type | # message of each type | ||||
self.message_count = defaultdict(int) | self.message_count = defaultdict(int) | ||||
self.last_message = {} | self.last_message = {} | ||||
▲ Show 20 Lines • Show All 154 Lines • ▼ Show 20 Lines | |||||
# Keep our own socket map for asyncore, so that we can track disconnects | # Keep our own socket map for asyncore, so that we can track disconnects | ||||
# ourselves (to workaround an issue with closing an asyncore socket when | # ourselves (to workaround an issue with closing an asyncore socket when | ||||
# using select) | # using select) | ||||
mininode_socket_map = dict() | mininode_socket_map = dict() | ||||
# One lock for synchronizing all data access between the networking thread (see | # One lock for synchronizing all data access between the networking thread (see | ||||
# NetworkThread below) and the thread running the test logic. For simplicity, | # NetworkThread below) and the thread running the test logic. For simplicity, | ||||
# NodeConn acquires this lock whenever delivering a message to a NodeConnCB, | # P2PConnection acquires this lock whenever delivering a message to a P2PInterface, | ||||
# and whenever adding anything to the send buffer (in send_message()). This | # and whenever adding anything to the send buffer (in send_message()). This | ||||
# lock should be acquired in the thread running the test logic to synchronize | # lock should be acquired in the thread running the test logic to synchronize | ||||
# access to any data shared with the NodeConnCB or NodeConn. | # access to any data shared with the P2PInterface or P2PConnection. | ||||
mininode_lock = RLock() | mininode_lock = RLock() | ||||
class NetworkThread(Thread): | class NetworkThread(Thread): | ||||
def run(self): | def run(self): | ||||
while mininode_socket_map: | while mininode_socket_map: | ||||
# We check for whether to disconnect outside of the asyncore | # We check for whether to disconnect outside of the asyncore | ||||
Show All 9 Lines |