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.
 
 
 
 

413 lines
14 KiB

#! /usr/bin/env python
'''test client-protocol interfacae.'''
import json
import jmbitcoin as bitcoin
import twisted
import base64
import pytest
import jmclient # noqa: F401 install asyncioreactor
from twisted.internet import reactor
from jmbase import get_log, bintohex
from jmbase.commands import *
from jmclient import load_test_config, Taker,\
JMClientProtocolFactory, jm_single, Maker, WalletService
from jmclient.client_protocol import JMTakerClientProtocol
from twisted.python.log import msg as tmsg
from twisted.internet import protocol, task
from twisted.internet.defer import inlineCallbacks
from twisted.internet.error import (ConnectionLost, ConnectionAborted,
ConnectionClosed, ConnectionDone)
from twisted.protocols.amp import UnknownRemoteError
from twisted.protocols import amp
from twisted.test import proto_helpers
from taker_test_data import t_raw_signed_tx
from commontest import default_max_cj_fee, TrialTestCase
pytestmark = pytest.mark.usefixtures("setup_regtest_bitcoind")
twisted.internet.base.DelayedCall.debug = True
test_completed = False
clientfactory = None
runno = 0
jlog = get_log()
def dummy_taker_finished(res, fromtx, waittime=0.0):
pass
class DummyTaker(Taker):
def set_fail_init(self, val):
self.failinit = val
def set_fail_utxos(self, val):
self.failutxos = val
def default_taker_info_callback(self, infotype, msg):
jlog.debug(infotype + ":" + msg)
async def initialize(self, orderbook, fidelity_bonds_info):
"""Once the daemon is active and has returned the current orderbook,
select offers, re-initialize variables and prepare a commitment,
then send it to the protocol to fill offers.
"""
if self.failinit==-1:
return (True, -1, "aa"*32, {'dummy':'revelation'},orderbook[:2])
elif self.failinit:
return (False,)
else:
return (True, 1000000, "aa"*32, {'dummy':'revelation'},
orderbook[:2])
async def receive_utxos(self, ioauth_data):
"""Triggered when the daemon returns utxo data from
makers who responded; this is the completion of phase 1
of the protocol
"""
if self.failutxos:
return (False, "dummyreason")
else:
return (True, [x*64 + ":01" for x in ["a", "b", "c"]],
base64.b16decode(t_raw_signed_tx, casefold=True))
async def on_sig(self, nick, sigb64):
"""For test, we exit 'early' on first message, since this marks the end
of client-server communication with the daemon.
"""
jlog.debug("We got a sig: " + sigb64)
end_test()
return None
class DummyWallet(object):
def get_wallet_id(self):
return 'aaaa'
#class DummyWalletService(object):
# wallet = DummyWallet()
# def register_callbacks(self, callbacks, unconfirmed=True):
# pass
class DummyMaker(Maker):
def __init__(self):
self.aborted = False
self.wallet_service = WalletService(DummyWallet())
self.offerlist = self.create_my_orders()
self.fidelity_bond = None
async def try_to_create_my_orders(self):
pass
async def on_auth_received(self, nick, offer, commitment, cr, amount, kphex):
# success, utxos, auth_pub, cj_addr, change_addr, btc_sig
return True, [], b"", '', '', ''
async def on_tx_received(self, nick, tx, offerinfo):
# success, sigs
return True, []
def verify_unsigned_tx(self, txd, offerinfo):
# success, errormsg
return True, None
def modify_orders(self, to_cancel, to_announce):
pass
def import_new_addresses(self, addr_list):
pass
def create_my_orders(self):
return [{
'cjfee': '0',
'maxsize': 100000000,
'minsize': 75000,
'oid': 0,
'ordertype': 'sw0reloffer',
'txfee': 0
}]
async def oid_to_order(self, cjorder, amount):
# utxos, cj_addr, change_addr
return [], '', ''
def on_tx_unconfirmed(self, cjorder, txid):
return [], []
def on_tx_confirmed(self, cjorder, txid, confirmations):
return [], []
class JMBaseProtocol(amp.AMP):
def checkClientResponse(self, response):
"""A generic check of client acceptance; any failure
is considered criticial.
"""
if 'accepted' not in response or not response['accepted']:
raise Exception("unexpected client response")
def defaultErrback(self, failure):
failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone,
ConnectionLost, UnknownRemoteError)
def defaultCallbacks(self, d):
d.addCallback(self.checkClientResponse)
d.addErrback(self.defaultErrback)
def show_receipt(name, *args):
tmsg("Received msgtype: " + name + ", 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()
reactor.callLater(1, end_client, client)
class JMTestServerProtocol(JMBaseProtocol):
@JMInit.responder
def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location):
show_receipt("JMINIT", bcsource, network, chan_configs, minmakers,
maker_timeout_sec, dust_threshold, blacklist_location)
d = self.callRemote(JMInitProto,
nick_hash_length=1,
nick_max_encoded=2,
joinmarket_nick_header="J",
joinmarket_version=5)
self.defaultCallbacks(d)
return {'accepted': True}
@JMStartMC.responder
def on_JM_START_MC(self, nick):
show_receipt("STARTMC", nick)
d = self.callRemote(JMUp)
self.defaultCallbacks(d)
return {'accepted': True}
@JMSetup.responder
def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
show_receipt("JMSETUP", role, initdata, use_fidelity_bond)
d = self.callRemote(JMSetupDone)
self.defaultCallbacks(d)
return {'accepted': True}
@JMRequestOffers.responder
def on_JM_REQUEST_OFFERS(self):
show_receipt("JMREQUESTOFFERS")
#build a huge orderbook to test BigString Argument
orderbook = ["aaaa" for _ in range(15)]
fidelitybonds = ["bbbb" for _ in range(15)]
d = self.callRemote(JMOffers,
orderbook=json.dumps(orderbook),
fidelitybonds=json.dumps(fidelitybonds))
self.defaultCallbacks(d)
return {'accepted': True}
@JMFill.responder
def on_JM_FILL(self, amount, commitment, revelation, filled_offers):
success = False if amount == -1 else True
show_receipt("JMFILL", amount, commitment, revelation, filled_offers)
d = self.callRemote(JMFillResponse,
success=success,
ioauth_data=['dummy', 'list'])
return {'accepted': True}
@JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, tx):
show_receipt("JMMAKETX", nick_list, tx)
d = self.callRemote(JMSigReceived,
nick="dummynick",
sig="xxxsig")
self.defaultCallbacks(d)
#add dummy calls to check message sign and message verify
d2 = self.callRemote(JMRequestMsgSig,
nick="dummynickforsign",
cmd="command1",
msg="msgforsign",
msg_to_be_signed="fullmsgforsign",
hostid="hostid1")
self.defaultCallbacks(d2)
#To test, this must include a valid ecdsa sig
fullmsg = "fullmsgforverify"
priv = b"\xaa"*32 + b"\x01"
pub = bintohex(bitcoin.privkey_to_pubkey(priv))
sig = bitcoin.ecdsa_sign(fullmsg, priv)
d3 = self.callRemote(JMRequestMsgSigVerify,
msg="msgforverify",
fullmsg=fullmsg,
sig=sig,
pubkey=pub,
nick="dummynickforverify",
hashlen=4,
max_encoded=5,
hostid="hostid2")
self.defaultCallbacks(d3)
d4 = self.callRemote(JMSigReceived,
nick="dummynick",
sig="dummysig")
self.defaultCallbacks(d4)
return {'accepted': True}
@JMMsgSignature.responder
def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid):
show_receipt("JMMSGSIGNATURE", nick, cmd, msg_to_return, hostid)
return {'accepted': True}
@JMMsgSignatureVerify.responder
def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid):
show_receipt("JMMSGSIGVERIFY", verif_result, nick, fullmsg, hostid)
return {'accepted': True}
class JMTestServerProtocolFactory(protocol.ServerFactory):
protocol = JMTestServerProtocol
class DummyClientProtocolFactory(JMClientProtocolFactory):
def buildProtocol(self, addr):
return JMTakerClientProtocol(self, self.client, nick_priv=b"\xaa"*32 + b"\x01")
class TrialTestJMClientProto(TrialTestCase):
def setUp(self):
global clientfactory
print("setUp()")
params = [[False, False], [True, False], [False, True], [-1, False]]
load_test_config()
jm_single().maker_timeout_sec = 1
self.port = reactor.listenTCP(28184, JMTestServerProtocolFactory())
self.addCleanup(self.port.stopListening)
def cb(client):
self.client = client
self.addCleanup(self.client.transport.loseConnection)
clientfactories = []
takers = [DummyTaker(
WalletService(DummyWallet()), ["a", "b"], default_max_cj_fee, callbacks=(
None, None, dummy_taker_finished)) for _ in range(len(params))]
for i, p in enumerate(params):
takers[i].set_fail_init(p[0])
takers[i].set_fail_utxos(p[1])
takers[i].testflag = True
if i != 0:
clientfactories.append(JMClientProtocolFactory(takers[i]))
clientconn = reactor.connectTCP("localhost", 28184,
clientfactories[i])
self.addCleanup(clientconn.disconnect)
else:
clientfactories.append(DummyClientProtocolFactory(takers[i]))
clientfactory = clientfactories[0]
clientconn = reactor.connectTCP("localhost", 28184,
clientfactories[0])
self.addCleanup(clientconn.disconnect)
def tearDown(self):
for dc in reactor.getDelayedCalls():
dc.cancel()
def test_waiter(self):
print("test_main()")
return task.deferLater(reactor, 3, self._called_by_deffered)
def _called_by_deffered(self):
pass
class TestMakerClientProtocol(TrialTestCase):
"""
very basic test case for JMMakerClientProtocol
This test does not do much useful things right now, other than making sure
no exceptions are fired. Most argument data is not crafted carefully and
may lead to failing tests if anything changes.
"""
def get_response_cb(self, cmd):
def response_cb(response):
parsed = cmd.parseResponse(response, self.client)
assert parsed['accepted'] is True
return response_cb
def callClient(self, jmcmd, response_cb=None, error_cb=None, **kwargs):
box = jmcmd.makeArguments(kwargs, self.client)
box[b'_command'] = jmcmd.__name__.encode('utf-8')
d = self.client.dispatchCommand(box)
d.addCallback(response_cb or self.get_response_cb(jmcmd))
if error_cb:
d.addErrback(error_cb)
return d
def setUp(self):
load_test_config()
factory = JMClientProtocolFactory(DummyMaker(), proto_type='MAKER')
self.client = factory.buildProtocol(None)
self.tr = proto_helpers.StringTransport()
self.client.makeConnection(self.tr)
def tearDown(self):
for dc in reactor.getDelayedCalls():
dc.cancel()
def init_client(self):
return self.callClient(
JMInitProto, nick_hash_length=1, nick_max_encoded=2,
joinmarket_nick_header='J', joinmarket_version=5)
@inlineCallbacks
def test_JMInitProto(self):
yield self.init_client()
@inlineCallbacks
def test_JMRequestMsgSig(self):
yield self.init_client()
yield self.callClient(
JMRequestMsgSig, nick='dummynickforsign', cmd='command1',
msg='msgforsign', msg_to_be_signed='fullmsgforsign',
hostid='hostid1')
@inlineCallbacks
def test_JMRequestMsgSigVerify(self):
fullmsg = 'fullmsgforverify'
priv = b"\xaa"*32 + b"\x01"
pub = bintohex(bitcoin.privkey_to_pubkey(priv))
sig = bitcoin.ecdsa_sign(fullmsg, priv)
yield self.init_client()
yield self.callClient(
JMRequestMsgSigVerify, msg='msgforverify', fullmsg=fullmsg,
sig=sig, pubkey=pub, nick='dummynickforverify', hashlen=4,
max_encoded=5, hostid='hostid2')
@inlineCallbacks
def test_JMUp(self):
yield self.init_client()
yield self.callClient(JMUp)
@inlineCallbacks
def test_JMSetupDone(self):
yield self.init_client()
yield self.callClient(JMSetupDone)
@inlineCallbacks
def test_JMAuthReceived(self):
yield self.init_client()
yield self.callClient(
JMAuthReceived, nick='testnick', offer={},
commitment='testcommitment', revelation={}, amount=100000,
kphex='testkphex')
@inlineCallbacks
def test_JMTXReceived(self):
yield self.init_client()
yield self.callClient(
JMTXReceived, nick='testnick',
tx=base64.b16decode(t_raw_signed_tx, casefold=True),
offer={"cjaddr":"2MwfecDHsQTm4Gg3RekQdpqAMR15BJrjfRF"})