You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

244 lines
8.4 KiB

# -*- coding: utf-8 -*-
import asyncio
import io
from electrum.plugins.joinmarket.jmbase import commands
from electrum.plugins.joinmarket.jmdaemon import (
OnionMessageChannel, MessageChannelCollection)
from electrum.plugins.joinmarket.jmdaemon.onionmc import (
PEER_STATUS_UNCONNECTED, PEER_STATUS_CONNECTED, PEER_STATUS_HANDSHAKED,
PEER_STATUS_DISCONNECTED, NOT_SERVING_ONION_HOSTNAME, OnionPeerError,
OnionCustomMessageDecodingError, OnionCustomMessage, JM_MESSAGE_TYPES)
from electrum.plugins.joinmarket.jmdaemon.onionmc_support import (
TorClientService)
from electrum.plugins.joinmarket.tests import JMTestCase
class DummyTransport(asyncio.Transport):
def __init__(self, logger):
super().__init__()
self._t = io.BytesIO(b'')
self.logger = logger
def write(self, data):
self.logger.debug(f'DummyTransport.write: {data}')
self._t.write(data)
def close(self):
self._t.close()
class DummyTorClientService(TorClientService):
async def _proxy_create_conn(self):
self.logger.debug(f'_proxy_create_conn: {self.factory.buildProtocol}, '
f'{self.host}:{self.port}')
self.transport = DummyTransport(self.logger)
self.protocol = self.factory.buildProtocol()
self.protocol.connection_made(self.transport)
class DummyMC(OnionMessageChannel):
def __init__(self, jmman, configdata, nick):
super().__init__(jmman, configdata,
client_service_cls=DummyTorClientService)
self.set_nick(nick)
class OnionBaseTestCase(JMTestCase):
async def on_connect(self, x):
print('simulated on-connect', x)
async def on_welcome(self, mc):
print('simulated on-welcome', mc)
if mc.nick == "irc_publisher":
d = commands.deferLater(3.0, self.junk_pubmsgs, mc)
d.addCallback(self.junk_longmsgs)
d.addCallback(self.junk_announce)
d.addCallback(self.junk_fill)
async def on_disconnect(self, x):
print('simulated on-disconnect', x)
def on_order_seen(self, dummy, counterparty, oid, ordertype, minsize,
maxsize, txfee, cjfee):
self.yg_name = counterparty
async def on_pubkey(self, pubkey):
print("received pubkey: " + pubkey)
async def junk_pubmsgs(self, mc):
# start a raw IRCMessageChannel instance in a thread;
# then call send_* on it with various errant messages
await asyncio.sleep(1)
await mc.request_orderbook()
await asyncio.sleep(1)
# now try directly
await mc.pubmsg("!orderbook")
await asyncio.sleep(1)
# should be ignored; can we check?
await mc.pubmsg("!orderbook!orderbook")
return mc
async def junk_longmsgs(self, mc):
# assuming MAX_PRIVMSG_LEN is not something crazy
# big like 550, this should fail
# with pytest.raises(AssertionError) as e_info:
await mc.pubmsg("junk and crap"*40)
await asyncio.sleep(1)
# assuming MAX_PRIVMSG_LEN is not something crazy
# small like 180, this should succeed
await mc.pubmsg("junk and crap"*15)
await asyncio.sleep(1)
return mc
async def junk_announce(self, mc):
# try a long order announcement in public
# because we don't want to build a real orderbook,
# call the underlying IRC announce function.
# TODO: how to test that the sent format was correct?
print('got here')
await mc._announce_orders(["!abc def gh 0001"]*30)
await asyncio.sleep(1)
return mc
async def junk_fill(self, mc):
cpname = "irc_receiver"
# send a fill with an invalid pubkey to the existing yg;
# this should trigger a NaclError but should NOT kill it.
await mc._privmsg(cpname, "fill", "0 10000000 abcdef")
# Try with ob flag
await mc._pubmsg("!reloffer stuff")
await asyncio.sleep(1)
# Trigger throttling with large messages
await mc._privmsg(cpname, "tx", "aa"*5000)
await asyncio.sleep(1)
# with pytest.raises(CJPeerError) as e_info:
await mc.send_error(cpname, "fly you fools!")
await asyncio.sleep(1)
return mc
async def asyncSetUp(self):
await super().asyncSetUp()
jmman = self.jmman
jmconf = jmman.jmconf
jmconf.maker_timeout_sec = 1
configdata = list(jmconf.get_msg_channels().values())[2]
directory_node = configdata['directory_nodes'].split(',')[0]
configdata['directory_nodes'] = directory_node
self.mc = mc = DummyMC(jmman, configdata, 'onionnick')
mc.register_orderbookwatch_callbacks(on_order_seen=self.on_order_seen)
mc.register_taker_callbacks(on_pubkey=self.on_pubkey)
mc.on_connect = self.on_connect
mc.on_disconnect = self.on_disconnect
mc.on_welcome = self.on_welcome
for p in self.mc.peers:
p.connect()
p._status = PEER_STATUS_HANDSHAKED
self.peer = list(self.mc.peers)[0]
self.mcc = MessageChannelCollection([self.mc], jmman)
await self.mcc.run()
async def asyncTearDown(self):
async def cb(m):
# don't try to reconnect
m.give_up = True
await m.shutdown()
await cb(self.mc)
class OnionCustomMessageTestCase(OnionBaseTestCase):
async def test_from_string_decode(self):
with self.assertRaises(OnionCustomMessageDecodingError):
OnionCustomMessage.from_string_decode(b'')
msg = OnionCustomMessage.from_string_decode(
b'{"line": "dummymsg", "type": 1}')
assert msg.text == 'dummymsg'
assert msg.msgtype == 1
class OnionClientFactoryTestCase(OnionBaseTestCase):
async def test_register_disconnection(self):
peer = self.peer
factory = peer.factory
factory.register_disconnection(peer.reconnecting_service.protocol)
async def test_send(self):
factory = self.peer.factory
msg = OnionCustomMessage('dummymsg', JM_MESSAGE_TYPES["pubmsg"])
assert factory.send(msg)
async def test_receive_message(self):
peer = self.peer
factory = peer.factory
msg = OnionCustomMessage('dummymsg', JM_MESSAGE_TYPES["pubmsg"])
factory.receive_message(msg, peer.reconnecting_service.protocol)
class OnionPeerTestCase(OnionBaseTestCase):
async def test_update_status(self):
peer = self.peer
assert peer.status() == PEER_STATUS_HANDSHAKED
peer._status = PEER_STATUS_UNCONNECTED
assert peer.status() == PEER_STATUS_UNCONNECTED
peer.update_status(PEER_STATUS_CONNECTED)
assert peer.status() == PEER_STATUS_CONNECTED
peer.update_status(PEER_STATUS_HANDSHAKED)
assert peer.status() == PEER_STATUS_HANDSHAKED
peer.update_status(PEER_STATUS_DISCONNECTED)
assert peer.status() == PEER_STATUS_DISCONNECTED
async def test_set_nick(self):
peer = self.peer
assert peer.nick == ''
peer.set_nick('dummynick')
assert peer.nick == 'dummynick'
async def test_get_nick_peerlocation_ser(self):
peer = self.peer
with self.assertRaises(OnionPeerError):
peer.get_nick_peerlocation_ser()
peer.set_nick('dummynick')
assert peer.get_nick_peerlocation_ser() == (
'dummynick;'
'rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad'
'.onion:5222')
async def test_set_location(self):
peer = self.peer
assert peer.set_location(NOT_SERVING_ONION_HOSTNAME)
assert not peer.set_location('host:port')
assert not peer.set_location('host:-5')
assert peer.set_location('host:5222')
async def test_peer_location(self):
peer = self.peer
assert peer.peer_location() == (
'rr6f6qtleiiwic45bby4zwmiwjrj3jsbmcvutwpqxjziaydjydkk5iad'
'.onion:5222')
assert peer.set_location('host:5222')
assert peer.peer_location() == 'host:5222'
async def test_send(self):
peer = self.peer
msg = OnionCustomMessage('dummymsg', JM_MESSAGE_TYPES["pubmsg"])
assert peer.send(msg)
async def test_receive_message(self):
msg = OnionCustomMessage('!dummymsg test', JM_MESSAGE_TYPES["pubmsg"])
peer = self.peer
await peer.receive_message(msg)
async def test_notify_message_unsendable(self):
peer = self.peer
peer.notify_message_unsendable()