Browse Source

test_client_protocol.py: enhance tests coverage

add-joinmarket
zebra-lucky 1 year ago
parent
commit
911d2fda38
  1. 2
      electrum/plugins/joinmarket/jm_qt.py
  2. 1
      electrum/plugins/joinmarket/jm_util.py
  3. 2
      electrum/plugins/joinmarket/jmbitcoin/secp256k1_main.py
  4. 2
      electrum/plugins/joinmarket/jmclient/client_protocol.py
  5. 206
      electrum/plugins/joinmarket/tests/jmclient/test_client_protocol.py

2
electrum/plugins/joinmarket/jm_qt.py

@ -6,7 +6,7 @@ import asyncio
from functools import partial from functools import partial
from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtCore import Qt, QTimer, pyqtSignal
from PyQt6.QtGui import (QIcon, QTextCursor, QIntValidator, QFont, from PyQt6.QtGui import (QTextCursor, QIntValidator, QFont,
QKeySequence, QColor, QBrush, QAction) QKeySequence, QColor, QBrush, QAction)
from PyQt6.QtWidgets import (QWidget, QGridLayout, QHBoxLayout, QLabel, from PyQt6.QtWidgets import (QWidget, QGridLayout, QHBoxLayout, QLabel,
QVBoxLayout, QDialog, QPushButton, QTabWidget, QVBoxLayout, QDialog, QPushButton, QTabWidget,

1
electrum/plugins/joinmarket/jm_util.py

@ -2,7 +2,6 @@
import attr import attr
import logging import logging
import re
from enum import IntEnum from enum import IntEnum
import electrum_ecc as ecc import electrum_ecc as ecc

2
electrum/plugins/joinmarket/jmbitcoin/secp256k1_main.py

@ -149,7 +149,7 @@ def multiply(s: bytes, pub: bytes, return_serialized: bool = True) -> bytes:
return res.get_public_key_bytes() return res.get_public_key_bytes()
def add_pubkeys(pubkeys: List[bytes]) -> ecc.ECPubkey: def add_pubkeys(pubkeys: List[bytes]) -> bytes:
'''Input a list of binary compressed pubkeys '''Input a list of binary compressed pubkeys
and return their sum as a binary compressed pubkey.''' and return their sum as a binary compressed pubkey.'''
pubkey_list = [ecc.ECPubkey(x) for x in pubkeys] pubkey_list = [ecc.ECPubkey(x) for x in pubkeys]

2
electrum/plugins/joinmarket/jmclient/client_protocol.py

@ -232,7 +232,7 @@ class JMTakerClientProtocol(JMClientProtocol):
return {'accepted': True} return {'accepted': True}
@commands.JMSetupDone.responder @commands.JMSetupDone.responder
def on_JM_SETUP_DONE(self): async def on_JM_SETUP_DONE(self):
self.logger.info("JM daemon setup complete") self.logger.info("JM daemon setup complete")
# The daemon is ready and has requested the orderbook # The daemon is ready and has requested the orderbook
# from the pit; we can request the entire orderbook # from the pit; we can request the entire orderbook

206
electrum/plugins/joinmarket/tests/jmclient/test_client_protocol.py

@ -11,28 +11,26 @@ from electrum.plugins.joinmarket.jmbase import commands
from electrum.plugins.joinmarket.jmbase import bintohex from electrum.plugins.joinmarket.jmbase import bintohex
from electrum.plugins.joinmarket import jmbitcoin as bitcoin from electrum.plugins.joinmarket import jmbitcoin as bitcoin
from electrum.plugins.joinmarket.jmclient import ( from electrum.plugins.joinmarket.jmclient import (
Taker, JMClientProtocolFactory, JMTakerClientProtocol) Taker, JMClientProtocolFactory, JMTakerClientProtocol, NO_ROUNDING,
get_max_cj_fee_values, fidelity_bond_weighted_order_choose)
from electrum.plugins.joinmarket.tests import JMTestCase from electrum.plugins.joinmarket.tests import JMTestCase
from .taker_test_data import t_raw_signed_tx from .taker_test_data import t_raw_signed_tx
from .commontest import default_max_cj_fee
test_completed = False
clientfactory = None
LOGGING_SHORTCUT = 'J' LOGGING_SHORTCUT = 'J'
jlog = get_logger(__name__) jlog = get_logger(__name__)
jlog.addFilter(ShortcutInjectingFilter(shortcut=LOGGING_SHORTCUT)) jlog.addFilter(ShortcutInjectingFilter(shortcut=LOGGING_SHORTCUT))
def dummy_taker_finished(res, fromtx, waittime=0.0):
pass
class DummyTaker(Taker): class DummyTaker(Taker):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.failutxos = 0
self.failinit = 0
def set_fail_init(self, val): def set_fail_init(self, val):
self.failinit = val self.failinit = val
@ -42,7 +40,7 @@ class DummyTaker(Taker):
def default_taker_info_callback(self, infotype, msg): def default_taker_info_callback(self, infotype, msg):
jlog.debug(infotype + ":" + msg) jlog.debug(infotype + ":" + msg)
def initialize(self, orderbook, fidelity_bonds_info): async def initialize(self, orderbook, fidelity_bonds_info):
"""Once the daemon is active and has returned the current orderbook, """Once the daemon is active and has returned the current orderbook,
select offers, re-initialize variables and prepare a commitment, select offers, re-initialize variables and prepare a commitment,
then send it to the protocol to fill offers. then send it to the protocol to fill offers.
@ -56,7 +54,7 @@ class DummyTaker(Taker):
return (True, 1000000, "aa"*32, {'dummy': 'revelation'}, return (True, 1000000, "aa"*32, {'dummy': 'revelation'},
orderbook[:2]) orderbook[:2])
def receive_utxos(self, ioauth_data): async def receive_utxos(self, ioauth_data):
"""Triggered when the daemon returns utxo data from """Triggered when the daemon returns utxo data from
makers who responded; this is the completion of phase 1 makers who responded; this is the completion of phase 1
of the protocol of the protocol
@ -67,20 +65,14 @@ class DummyTaker(Taker):
return (True, [x*64 + ":01" for x in ["a", "b", "c"]], return (True, [x*64 + ":01" for x in ["a", "b", "c"]],
base64.b16decode(t_raw_signed_tx, casefold=True)) base64.b16decode(t_raw_signed_tx, casefold=True))
def on_sig(self, nick, sigb64): async def on_sig(self, nick, sigb64):
"""For test, we exit 'early' on first message, since this marks the end """For test, we exit 'early' on first message, since this marks the end
of client-server communication with the daemon. of client-server communication with the daemon.
""" """
jlog.debug("We got a sig: " + sigb64) jlog.debug("We got a sig: " + sigb64)
end_test()
return None return None
class DummyWallet(object):
def get_wallet_id(self):
return 'aaaa'
class JMBaseProtocol(commands.CallRemoteMock): class JMBaseProtocol(commands.CallRemoteMock):
def checkClientResponse(self, response): def checkClientResponse(self, response):
@ -103,77 +95,73 @@ def show_receipt(name, *args):
",".join([str(x) for x in args])) ",".join([str(x) for x in args]))
def end_client(client):
pass
def end_test():
global test_completed
test_completed = True
client = clientfactory.getClient()
commands.callLater(1, end_client, client)
class JMTestServerProtocol(JMBaseProtocol): class JMTestServerProtocol(JMBaseProtocol):
@commands.JMInit.responder @commands.JMInit.responder
def on_JM_INIT(self, bcsource, network, chan_configs, minmakers, async def on_JM_INIT(self, bcsource, network, chan_configs,
maker_timeout_sec, dust_threshold, blacklist_location): minmakers, maker_timeout_sec,
dust_threshold, blacklist_location):
show_receipt("JMINIT", bcsource, network, chan_configs, minmakers, show_receipt("JMINIT", bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location) maker_timeout_sec, dust_threshold, blacklist_location)
d = self.callRemote(commands.JMInitProto, d = await self.callRemote(
commands.JMInitProto,
self.factory.proto_client,
nick_hash_length=1, nick_hash_length=1,
nick_max_encoded=2, nick_max_encoded=2,
joinmarket_nick_header="J", joinmarket_nick_header="J",
joinmarket_version=5) joinmarket_version=5
)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@commands.JMStartMC.responder @commands.JMStartMC.responder
def on_JM_START_MC(self, nick): async def on_JM_START_MC(self, nick):
show_receipt("STARTMC", nick) show_receipt("STARTMC", nick)
d = self.callRemote(commands.JMUp) d = self.callRemote(commands.JMUp)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@commands.JMSetup.responder @commands.JMSetup.responder
def on_JM_SETUP(self, role, initdata, use_fidelity_bond): async def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
show_receipt("JMSETUP", role, initdata, use_fidelity_bond) show_receipt("JMSETUP", role, initdata, use_fidelity_bond)
d = self.callRemote(commands.JMSetupDone) d = await self.callRemote(commands.JMSetupDone,
self.factory.proto_client)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@commands.JMRequestOffers.responder @commands.JMRequestOffers.responder
def on_JM_REQUEST_OFFERS(self): async def on_JM_REQUEST_OFFERS(self):
show_receipt("JMREQUESTOFFERS") show_receipt("JMREQUESTOFFERS")
# build a huge orderbook to test BigString Argument # build a huge orderbook to test BigString Argument
orderbook = ["aaaa" for _ in range(15)] orderbook = ["aaaa" for _ in range(15)]
fidelitybonds = ["bbbb" for _ in range(15)] fidelitybonds = ["bbbb" for _ in range(15)]
d = self.callRemote(commands.JMOffers, d = await self.callRemote(commands.JMOffers,
self.factory.proto_client,
orderbook=json.dumps(orderbook), orderbook=json.dumps(orderbook),
fidelitybonds=json.dumps(fidelitybonds)) fidelitybonds=json.dumps(fidelitybonds))
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@commands.JMFill.responder @commands.JMFill.responder
def on_JM_FILL(self, amount, commitment, revelation, filled_offers): async def on_JM_FILL(self, amount, commitment, revelation, filled_offers):
success = False if amount == -1 else True success = False if amount == -1 else True
show_receipt("JMFILL", amount, commitment, revelation, filled_offers) show_receipt("JMFILL", amount, commitment, revelation, filled_offers)
d = self.callRemote(commands.JMFillResponse, d = await self.callRemote(commands.JMFillResponse,
success=success, self.factory.proto_client, success=success,
ioauth_data=['dummy', 'list']) ioauth_data=['dummy', 'list'])
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@commands.JMMakeTx.responder @commands.JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, tx): async def on_JM_MAKE_TX(self, nick_list, tx):
show_receipt("JMMAKETX", nick_list, tx) show_receipt("JMMAKETX", nick_list, tx)
d = self.callRemote(commands.JMSigReceived, d = await self.callRemote(commands.JMSigReceived,
nick="dummynick", self.factory.proto_client,
sig="xxxsig") nick="dummynick", sig="xxxsig")
self.defaultCallbacks(d) self.defaultCallbacks(d)
# add dummy calls to check message sign and message verify # add dummy calls to check message sign and message verify
d2 = self.callRemote(commands.JMRequestMsgSig, d2 = await self.callRemote(commands.JMRequestMsgSig,
self.factory.proto_client,
nick="dummynickforsign", nick="dummynickforsign",
cmd="command1", cmd="command1",
msg="msgforsign", msg="msgforsign",
@ -183,9 +171,10 @@ class JMTestServerProtocol(JMBaseProtocol):
# To test, this must include a valid ecdsa sig # To test, this must include a valid ecdsa sig
fullmsg = "fullmsgforverify" fullmsg = "fullmsgforverify"
priv = b"\xaa"*32 + b"\x01" priv = b"\xaa"*32 + b"\x01"
pub = bintohex(bitcoin.privkey_to_pubkey(priv)) pub = bintohex(bitcoin.privkey_to_pubkey(priv).get_public_key_bytes())
sig = bitcoin.ecdsa_sign(fullmsg, priv) sig = bitcoin.ecdsa_sign(fullmsg, priv)
d3 = self.callRemote(commands.JMRequestMsgSigVerify, d3 = await self.callRemote(commands.JMRequestMsgSigVerify,
self.factory.proto_client,
msg="msgforverify", msg="msgforverify",
fullmsg=fullmsg, fullmsg=fullmsg,
sig=sig, sig=sig,
@ -195,62 +184,107 @@ class JMTestServerProtocol(JMBaseProtocol):
max_encoded=5, max_encoded=5,
hostid="hostid2") hostid="hostid2")
self.defaultCallbacks(d3) self.defaultCallbacks(d3)
d4 = self.callRemote(commands.JMSigReceived, d4 = await self.callRemote(commands.JMSigReceived,
nick="dummynick", self.factory.proto_client,
sig="dummysig") nick="dummynick", sig="dummysig")
self.defaultCallbacks(d4) self.defaultCallbacks(d4)
return {'accepted': True} return {'accepted': True}
@commands.JMPushTx.responder
async def on_JM_PushTx(self, nick, tx):
show_receipt("JMPUSHTX", nick, tx)
return {'accepted': True}
@commands.JMMsgSignature.responder @commands.JMMsgSignature.responder
def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid): async def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid):
show_receipt("JMMSGSIGNATURE", nick, cmd, msg_to_return, hostid) show_receipt("JMMSGSIGNATURE", nick, cmd, msg_to_return, hostid)
return {'accepted': True} return {'accepted': True}
@commands.JMMsgSignatureVerify.responder @commands.JMMsgSignatureVerify.responder
def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid): async def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg,
hostid):
show_receipt("JMMSGSIGVERIFY", verif_result, nick, fullmsg, hostid) show_receipt("JMMSGSIGVERIFY", verif_result, nick, fullmsg, hostid)
return {'accepted': True} return {'accepted': True}
class JMTestServerProtocolFactory: class BaseClientProtocolTestCase(JMTestCase):
protocol = JMTestServerProtocol
class DummyClientProtocolFactory(JMClientProtocolFactory): def check_offers_callback(self, *args):
print('check_offers_callback', args)
def buildProtocol(self): def taker_info_callback(self, *args):
return JMTakerClientProtocol(self, self.client, print('taker_info_callback', args)
nick_priv=b"\xaa"*32 + b"\x01")
def taker_finished_callback(self, *args):
class TrialTestJMClientProto(JMTestCase): print('taker_finished_callback', args)
async def asyncSetUp(self): async def asyncSetUp(self):
await super().asyncSetUp() await super().asyncSetUp()
global clientfactory jmman = self.jmman
params = [[False, False], [True, False], [False, True], [-1, False]] jmconf = self.jmconf
self.jmman.maker_timeout_sec = 1 jmconf.maker_timeout_sec = 1
clientfactories = [] jmconf.max_cj_fee_confirmed = True
takers = [ self.schedule = [[0, 0, 2, 'INTERNAL', 0, NO_ROUNDING, 0]]
DummyTaker( self.maxcjfee = get_max_cj_fee_values(jmman, None)
self.jmman, self.destaddrs = []
["a", "b"], default_max_cj_fee, self.taker = DummyTaker(
callbacks=(None, None, dummy_taker_finished)) jmman,
for _ in range(len(params))] self.schedule,
for i, p in enumerate(params): self.maxcjfee,
takers[i].set_fail_init(p[0]) order_chooser=fidelity_bond_weighted_order_choose,
takers[i].set_fail_utxos(p[1]) callbacks=[self.check_offers_callback,
takers[i].testflag = True self.taker_info_callback,
if i != 0: self.taker_finished_callback],
clientfactories.append(JMClientProtocolFactory(takers[i])) tdestaddrs=self.destaddrs,
else: custom_change_address=None,
clientfactories.append(DummyClientProtocolFactory(takers[i])) ignored_makers=jmman.jmw.ignored_makers
clientfactory = clientfactories[0] )
self.clientfactory = JMClientProtocolFactory(self.taker)
jmman.set_client_factory(self.clientfactory)
self.client_proto = self.clientfactory.getClient()
class ClientProtocolTestCase(BaseClientProtocolTestCase):
async def test_on_JM_REQUEST_MSGSIG(self):
await self.client_proto.on_JM_REQUEST_MSGSIG(
nick="dummynickforsign", cmd="command1", msg="msgforsign",
msg_to_be_signed="fullmsgforsign", hostid="hostid1")
assert 0
class TakerClientProtocolTestCase(BaseClientProtocolTestCase):
async def test_clientStart(self):
await self.client_proto.clientStart()
async def test_stallMonitor(self):
self.client_proto.stallMonitor(0)
async def test_on_JM_UP(self):
await self.client_proto.on_JM_UP()
async def test_on_JM_SETUP_DONE(self):
await self.client_proto.on_JM_SETUP_DONE()
async def test_on_JM_FILL_RESPONSE(self):
await self.client_proto.on_JM_FILL_RESPONSE(
success=True, ioauth_data={'dummy': 'ioauth'})
async def test_on_JM_OFFERS(self):
orderbook = ["aaaa" for _ in range(15)]
fidelitybonds = ["bbbb" for _ in range(15)]
await self.client_proto.on_JM_OFFERS(
orderbook=json.dumps(orderbook),
fidelitybonds=json.dumps(fidelitybonds))
async def test_on_JM_SIG_RECEIVED(self):
await self.client_proto.on_JM_SIG_RECEIVED(
nick="dummynick", sig="xxxsig")
async def test_waiter(self): async def test_get_offers(self):
return commands.deferLater(3, self._called_by_deffered) await self.client_proto.get_offers()
async def _called_by_deffered(self): async def test_push_tx(self):
pass await self.client_proto.push_tx("dummynick", b"deadbeef")

Loading…
Cancel
Save