Browse Source

Merge #914: move client/server data encoding to twisted

2fdebb8 do not call reactor.stop() in test_commands.py (undeath)
e082c3c remove unneeded hex encoding/decoding from sent_tx + push_tx (undeath)
dfc82ab various whitespace fixes (undeath)
7dcd3f3 move client/server data encoding to twisted (undeath)
master
Adam Gibson 5 years ago
parent
commit
717d3145c5
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 10
      jmbase/jmbase/arguments.py
  2. 50
      jmbase/jmbase/commands.py
  3. 62
      jmbase/test/test_commands.py
  4. 82
      jmclient/jmclient/client_protocol.py
  5. 6
      jmclient/jmclient/maker.py
  6. 4
      jmclient/jmclient/taker.py
  7. 65
      jmclient/test/test_client_protocol.py
  8. 6
      jmclient/test/test_coinjoin.py
  9. 207
      jmdaemon/jmdaemon/daemon_protocol.py
  10. 25
      jmdaemon/jmdaemon/message_channel.py
  11. 57
      jmdaemon/test/test_daemon_protocol.py
  12. 8
      jmdaemon/test/test_message_channel.py
  13. 8
      test/ygrunner.py

10
jmbase/jmbase/arguments.py

@ -0,0 +1,10 @@
import json
from twisted.protocols.amp import String
class JsonEncodable(String):
def toString(self, inObject):
return super().toString(json.dumps(inObject).encode('ascii'))
def fromString(self, inString):
return super().fromString(json.loads(inString))

50
jmbase/jmbase/commands.py

@ -3,8 +3,10 @@ Commands defining client-server (daemon)
messaging protocol (*not* Joinmarket p2p protocol). messaging protocol (*not* Joinmarket p2p protocol).
Used for AMP asynchronous messages. Used for AMP asynchronous messages.
""" """
from twisted.protocols.amp import Boolean, Command, Integer, Unicode from twisted.protocols.amp import Boolean, Command, Integer, Unicode, ListOf,\
String
from .bigstring import BigUnicode from .bigstring import BigUnicode
from .arguments import JsonEncodable
class DaemonNotReady(Exception): class DaemonNotReady(Exception):
@ -29,7 +31,7 @@ class JMInit(JMCommand):
""" """
arguments = [(b'bcsource', Unicode()), arguments = [(b'bcsource', Unicode()),
(b'network', Unicode()), (b'network', Unicode()),
(b'irc_configs', Unicode()), (b'irc_configs', JsonEncodable()),
(b'minmakers', Integer()), (b'minmakers', Integer()),
(b'maker_timeout_sec', Integer()), (b'maker_timeout_sec', Integer()),
(b'dust_threshold', Integer())] (b'dust_threshold', Integer())]
@ -47,7 +49,7 @@ class JMSetup(JMCommand):
role, passes initial offers for announcement (for TAKER, this data is "none") role, passes initial offers for announcement (for TAKER, this data is "none")
""" """
arguments = [(b'role', Unicode()), arguments = [(b'role', Unicode()),
(b'offers', Unicode()), (b'initdata', JsonEncodable()),
(b'use_fidelity_bond', Boolean())] (b'use_fidelity_bond', Boolean())]
class JMMsgSignature(JMCommand): class JMMsgSignature(JMCommand):
@ -82,21 +84,21 @@ class JMFill(JMCommand):
arguments = [(b'amount', Integer()), arguments = [(b'amount', Integer()),
(b'commitment', Unicode()), (b'commitment', Unicode()),
(b'revelation', Unicode()), (b'revelation', Unicode()),
(b'filled_offers', Unicode())] (b'filled_offers', JsonEncodable())]
class JMMakeTx(JMCommand): class JMMakeTx(JMCommand):
"""Send a hex encoded raw bitcoin transaction """Send a hex encoded raw bitcoin transaction
to a set of counterparties to a set of counterparties
""" """
arguments = [(b'nick_list', Unicode()), arguments = [(b'nick_list', ListOf(Unicode())),
(b'txhex', Unicode())] (b'tx', String())]
class JMPushTx(JMCommand): class JMPushTx(JMCommand):
"""Pass a raw hex transaction to a specific """Pass a raw hex transaction to a specific
counterparty (maker) for pushing (anonymity feature in JM) counterparty (maker) for pushing (anonymity feature in JM)
""" """
arguments = [(b'nick', Unicode()), arguments = [(b'nick', Unicode()),
(b'txhex', Unicode())] (b'tx', String())]
"""MAKER specific commands """MAKER specific commands
""" """
@ -106,9 +108,9 @@ class JMAnnounceOffers(JMCommand):
to the daemon, along with new announcement to the daemon, along with new announcement
and cancellation lists (deltas). and cancellation lists (deltas).
""" """
arguments = [(b'to_announce', Unicode()), arguments = [(b'to_announce', JsonEncodable()),
(b'to_cancel', Unicode()), (b'to_cancel', JsonEncodable()),
(b'offerlist', Unicode())] (b'offerlist', JsonEncodable())]
class JMFidelityBondProof(JMCommand): class JMFidelityBondProof(JMCommand):
"""Send requested fidelity bond proof message""" """Send requested fidelity bond proof message"""
@ -120,7 +122,7 @@ class JMIOAuth(JMCommand):
verifying Taker's auth message verifying Taker's auth message
""" """
arguments = [(b'nick', Unicode()), arguments = [(b'nick', Unicode()),
(b'utxolist', Unicode()), (b'utxolist', JsonEncodable()),
(b'pubkey', Unicode()), (b'pubkey', Unicode()),
(b'cjaddr', Unicode()), (b'cjaddr', Unicode()),
(b'changeaddr', Unicode()), (b'changeaddr', Unicode()),
@ -131,7 +133,7 @@ class JMTXSigs(JMCommand):
sent by TAKER sent by TAKER
""" """
arguments = [(b'nick', Unicode()), arguments = [(b'nick', Unicode()),
(b'sigs', Unicode())] (b'sigs', ListOf(Unicode()))]
"""COMMANDS FROM DAEMON TO CLIENT """COMMANDS FROM DAEMON TO CLIENT
================================= =================================
@ -197,7 +199,7 @@ class JMFillResponse(JMCommand):
"""Returns ioauth data from MAKER if successful. """Returns ioauth data from MAKER if successful.
""" """
arguments = [(b'success', Boolean()), arguments = [(b'success', Boolean()),
(b'ioauth_data', Unicode())] (b'ioauth_data', JsonEncodable())]
class JMSigReceived(JMCommand): class JMSigReceived(JMCommand):
"""Returns an individual bitcoin transaction signature """Returns an individual bitcoin transaction signature
@ -221,9 +223,9 @@ class JMAuthReceived(JMCommand):
before setting up encryption and continuing. before setting up encryption and continuing.
""" """
arguments = [(b'nick', Unicode()), arguments = [(b'nick', Unicode()),
(b'offer', Unicode()), (b'offer', JsonEncodable()),
(b'commitment', Unicode()), (b'commitment', Unicode()),
(b'revelation', Unicode()), (b'revelation', JsonEncodable()),
(b'amount', Integer()), (b'amount', Integer()),
(b'kphex', Unicode())] (b'kphex', Unicode())]
@ -232,8 +234,8 @@ class JMTXReceived(JMCommand):
by TAKER, along with offerdata to verify fees. by TAKER, along with offerdata to verify fees.
""" """
arguments = [(b'nick', Unicode()), arguments = [(b'nick', Unicode()),
(b'txhex', Unicode()), (b'tx', String()),
(b'offer', Unicode())] (b'offer', JsonEncodable())]
class JMTXBroadcast(JMCommand): class JMTXBroadcast(JMCommand):
""" Accept a bitcoin transaction """ Accept a bitcoin transaction
@ -241,7 +243,7 @@ class JMTXBroadcast(JMCommand):
and relay it to the client for network and relay it to the client for network
broadcast. broadcast.
""" """
arguments = [(b'txhex', Unicode())] arguments = [(b'tx', String())]
"""SNICKER related commands. """SNICKER related commands.
""" """
@ -251,12 +253,12 @@ class SNICKERReceiverInit(JMCommand):
See documentation of `netconfig` in See documentation of `netconfig` in
jmdaemon.HTTPPassThrough.on_INIT jmdaemon.HTTPPassThrough.on_INIT
""" """
arguments = [(b'netconfig', Unicode())] arguments = [(b'netconfig', JsonEncodable())]
class SNICKERProposerInit(JMCommand): class SNICKERProposerInit(JMCommand):
""" As for receiver. """ As for receiver.
""" """
arguments = [(b'netconfig', Unicode())] arguments = [(b'netconfig', JsonEncodable())]
class SNICKERReceiverUp(JMCommand): class SNICKERReceiverUp(JMCommand):
arguments = [] arguments = []
@ -307,7 +309,7 @@ class BIP78SenderInit(JMCommand):
See documentation of `netconfig` in See documentation of `netconfig` in
jmdaemon.HTTPPassThrough.on_INIT jmdaemon.HTTPPassThrough.on_INIT
""" """
arguments = [(b'netconfig', Unicode())] arguments = [(b'netconfig', JsonEncodable())]
class BIP78SenderUp(JMCommand): class BIP78SenderUp(JMCommand):
arguments = [] arguments = []
@ -319,7 +321,7 @@ class BIP78SenderOriginalPSBT(JMCommand):
to be sent as an http request to the receiver. to be sent as an http request to the receiver.
""" """
arguments = [(b'body', BigUnicode()), arguments = [(b'body', BigUnicode()),
(b'params', Unicode())] (b'params', JsonEncodable())]
class BIP78SenderReceiveProposal(JMCommand): class BIP78SenderReceiveProposal(JMCommand):
""" Sends the payjoin proposal PSBT, received """ Sends the payjoin proposal PSBT, received
@ -341,7 +343,7 @@ class BIP78SenderReceiveError(JMCommand):
class BIP78ReceiverInit(JMCommand): class BIP78ReceiverInit(JMCommand):
""" Initialization data for a BIP78 hidden service. """ Initialization data for a BIP78 hidden service.
""" """
arguments = [(b'netconfig', Unicode())] arguments = [(b'netconfig', JsonEncodable())]
class BIP78ReceiverUp(JMCommand): class BIP78ReceiverUp(JMCommand):
""" Returns onion hostname to client when """ Returns onion hostname to client when
@ -356,7 +358,7 @@ class BIP78ReceiverOriginalPSBT(JMCommand):
parameters in the url, from the daemon to the client. parameters in the url, from the daemon to the client.
""" """
arguments = [(b'body', BigUnicode()), arguments = [(b'body', BigUnicode()),
(b'params', Unicode())] (b'params', JsonEncodable())]
class BIP78ReceiverSendProposal(JMCommand): class BIP78ReceiverSendProposal(JMCommand):
""" Receives a payjoin proposal PSBT from """ Receives a payjoin proposal PSBT from

62
jmbase/test/test_commands.py

@ -21,12 +21,12 @@ class JMBaseProtocol(amp.AMP):
is considered criticial. is considered criticial.
""" """
if 'accepted' not in response or not response['accepted']: if 'accepted' not in response or not response['accepted']:
reactor.stop() raise Exception(response)
def defaultErrback(self, failure): def defaultErrback(self, failure):
failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone, failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone,
ConnectionLost, UnknownRemoteError) ConnectionLost, UnknownRemoteError)
reactor.stop() raise Exception(failure)
def defaultCallbacks(self, d): def defaultCallbacks(self, d):
d.addCallback(self.checkClientResponse) d.addCallback(self.checkClientResponse)
@ -63,8 +63,8 @@ class JMTestServerProtocol(JMBaseProtocol):
return {'accepted': True} return {'accepted': True}
@JMSetup.responder @JMSetup.responder
def on_JM_SETUP(self, role, offers, use_fidelity_bond): def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
show_receipt("JMSETUP", role, offers, use_fidelity_bond) show_receipt("JMSETUP", role, initdata, use_fidelity_bond)
d = self.callRemote(JMSetupDone) d = self.callRemote(JMSetupDone)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -84,36 +84,36 @@ class JMTestServerProtocol(JMBaseProtocol):
def on_JM_FILL(self, amount, commitment, revelation, filled_offers): def on_JM_FILL(self, amount, commitment, revelation, filled_offers):
show_receipt("JMFILL", amount, commitment, revelation, filled_offers) show_receipt("JMFILL", amount, commitment, revelation, filled_offers)
d = self.callRemote(JMFillResponse, d = self.callRemote(JMFillResponse,
success=True, success=True,
ioauth_data = json.dumps(['dummy', 'list'])) ioauth_data=['dummy', 'list'])
return {'accepted': True} return {'accepted': True}
@JMMakeTx.responder @JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, txhex): def on_JM_MAKE_TX(self, nick_list, tx):
show_receipt("JMMAKETX", nick_list, txhex) show_receipt("JMMAKETX", nick_list, tx)
d = self.callRemote(JMSigReceived, d = self.callRemote(JMSigReceived,
nick="dummynick", nick="dummynick",
sig="xxxsig") 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(JMRequestMsgSig, d2 = self.callRemote(JMRequestMsgSig,
nick="dummynickforsign", nick="dummynickforsign",
cmd="command1", cmd="command1",
msg="msgforsign", msg="msgforsign",
msg_to_be_signed="fullmsgforsign", msg_to_be_signed="fullmsgforsign",
hostid="hostid1") hostid="hostid1")
self.defaultCallbacks(d2) self.defaultCallbacks(d2)
d3 = self.callRemote(JMRequestMsgSigVerify, d3 = self.callRemote(JMRequestMsgSigVerify,
msg="msgforverify", msg="msgforverify",
fullmsg="fullmsgforverify", fullmsg="fullmsgforverify",
sig="xxxsigforverify", sig="xxxsigforverify",
pubkey="pubkey1", pubkey="pubkey1",
nick="dummynickforverify", nick="dummynickforverify",
hashlen=4, hashlen=4,
max_encoded=5, max_encoded=5,
hostid="hostid2") hostid="hostid2")
self.defaultCallbacks(d3) self.defaultCallbacks(d3)
d4 = self.callRemote(JMTXBroadcast, txhex="deadbeef") d4 = self.callRemote(JMTXBroadcast, tx=b"deadbeef")
self.defaultCallbacks(d4) self.defaultCallbacks(d4)
return {'accepted': True} return {'accepted': True}
@ -137,7 +137,7 @@ class JMTestClientProtocol(JMBaseProtocol):
d = self.callRemote(JMInit, d = self.callRemote(JMInit,
bcsource="dummyblockchain", bcsource="dummyblockchain",
network="dummynetwork", network="dummynetwork",
irc_configs=json.dumps(['dummy', 'irc', 'config']), irc_configs=['dummy', 'irc', 'config'],
minmakers=7, minmakers=7,
maker_timeout_sec=8, maker_timeout_sec=8,
dust_threshold=1500) dust_threshold=1500)
@ -158,7 +158,7 @@ class JMTestClientProtocol(JMBaseProtocol):
show_receipt("JMUP") show_receipt("JMUP")
d = self.callRemote(JMSetup, d = self.callRemote(JMSetup,
role="TAKER", role="TAKER",
offers="{}", initdata=None,
use_fidelity_bond=False) use_fidelity_bond=False)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -174,8 +174,8 @@ class JMTestClientProtocol(JMBaseProtocol):
def on_JM_FILL_RESPONSE(self, success, ioauth_data): def on_JM_FILL_RESPONSE(self, success, ioauth_data):
show_receipt("JMFILLRESPONSE", success, ioauth_data) show_receipt("JMFILLRESPONSE", success, ioauth_data)
d = self.callRemote(JMMakeTx, d = self.callRemote(JMMakeTx,
nick_list= json.dumps(['nick1', 'nick2', 'nick3']), nick_list=['nick1', 'nick2', 'nick3'],
txhex="deadbeef") tx=b"deadbeef")
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -186,7 +186,7 @@ class JMTestClientProtocol(JMBaseProtocol):
amount=100, amount=100,
commitment="dummycommitment", commitment="dummycommitment",
revelation="dummyrevelation", revelation="dummyrevelation",
filled_offers=json.dumps(['list', 'of', 'filled', 'offers'])) filled_offers=['list', 'of', 'filled', 'offers'])
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -222,8 +222,8 @@ class JMTestClientProtocol(JMBaseProtocol):
return {'accepted': True} return {'accepted': True}
@JMTXBroadcast.responder @JMTXBroadcast.responder
def on_JM_TX_BROADCAST(self, txhex): def on_JM_TX_BROADCAST(self, tx):
show_receipt("JMTXBROADCAST", txhex) show_receipt("JMTXBROADCAST", tx)
return {"accepted": True} return {"accepted": True}
class JMTestClientProtocolFactory(protocol.ClientFactory): class JMTestClientProtocolFactory(protocol.ClientFactory):

82
jmclient/jmclient/client_protocol.py

@ -73,13 +73,13 @@ class BIP78ClientProtocol(BaseClientProtocol):
"tls_whitelist": ",".join(self.tls_whitelist), "tls_whitelist": ",".join(self.tls_whitelist),
"servers": [self.manager.server]} "servers": [self.manager.server]}
d = self.callRemote(commands.BIP78SenderInit, d = self.callRemote(commands.BIP78SenderInit,
netconfig=json.dumps(netconfig)) netconfig=netconfig)
else: else:
netconfig = {"port": 80, netconfig = {"port": 80,
"tor_control_host": jcg("PAYJOIN", "tor_control_host"), "tor_control_host": jcg("PAYJOIN", "tor_control_host"),
"tor_control_port": jcg("PAYJOIN", "tor_control_port")} "tor_control_port": jcg("PAYJOIN", "tor_control_port")}
d = self.callRemote(commands.BIP78ReceiverInit, d = self.callRemote(commands.BIP78ReceiverInit,
netconfig=json.dumps(netconfig)) netconfig=netconfig)
self.defaultCallbacks(d) self.defaultCallbacks(d)
@commands.BIP78ReceiverUp.responder @commands.BIP78ReceiverUp.responder
@ -89,7 +89,6 @@ class BIP78ClientProtocol(BaseClientProtocol):
@commands.BIP78ReceiverOriginalPSBT.responder @commands.BIP78ReceiverOriginalPSBT.responder
def on_BIP78_RECEIVER_ORIGINAL_PSBT(self, body, params): def on_BIP78_RECEIVER_ORIGINAL_PSBT(self, body, params):
params = json.loads(params)
# TODO: we don't need binary key/vals client side, but will have to edit # TODO: we don't need binary key/vals client side, but will have to edit
# PayjoinConverter for that: # PayjoinConverter for that:
retval = self.success_callback(body.encode("utf-8"), bdict_sdict_convert( retval = self.success_callback(body.encode("utf-8"), bdict_sdict_convert(
@ -121,7 +120,7 @@ class BIP78ClientProtocol(BaseClientProtocol):
def on_BIP78_SENDER_UP(self): def on_BIP78_SENDER_UP(self):
d = self.callRemote(commands.BIP78SenderOriginalPSBT, d = self.callRemote(commands.BIP78SenderOriginalPSBT,
body=self.manager.initial_psbt.to_base64(), body=self.manager.initial_psbt.to_base64(),
params=json.dumps(self.params)) params=self.params)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {"accepted": True} return {"accepted": True}
@ -168,10 +167,10 @@ class SNICKERClientProtocol(BaseClientProtocol):
if isinstance(self.client, SNICKERReceiver): if isinstance(self.client, SNICKERReceiver):
d = self.callRemote(commands.SNICKERReceiverInit, d = self.callRemote(commands.SNICKERReceiverInit,
netconfig=json.dumps(netconfig)) netconfig=netconfig)
else: else:
d = self.callRemote(commands.SNICKERProposerInit, d = self.callRemote(commands.SNICKERProposerInit,
netconfig=json.dumps(netconfig)) netconfig=netconfig)
self.defaultCallbacks(d) self.defaultCallbacks(d)
def shutdown(self): def shutdown(self):
@ -361,10 +360,10 @@ class JMClientProtocol(BaseClientProtocol):
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
def make_tx(self, nick_list, txhex): def make_tx(self, nick_list, tx):
d = self.callRemote(commands.JMMakeTx, d = self.callRemote(commands.JMMakeTx,
nick_list= json.dumps(nick_list), nick_list=nick_list,
txhex=txhex) tx=tx)
self.defaultCallbacks(d) self.defaultCallbacks(d)
class JMMakerClientProtocol(JMClientProtocol): class JMMakerClientProtocol(JMClientProtocol):
@ -394,7 +393,7 @@ class JMMakerClientProtocol(JMClientProtocol):
self.offers_ready_loop.stop() self.offers_ready_loop.stop()
d = self.callRemote(commands.JMSetup, d = self.callRemote(commands.JMSetup,
role="MAKER", role="MAKER",
offers=json.dumps(self.client.offerlist), initdata=self.client.offerlist,
use_fidelity_bond=(self.client.fidelity_bond is not None)) use_fidelity_bond=(self.client.fidelity_bond is not None))
self.defaultCallbacks(d) self.defaultCallbacks(d)
@ -423,7 +422,7 @@ class JMMakerClientProtocol(JMClientProtocol):
d = self.callRemote(commands.JMInit, d = self.callRemote(commands.JMInit,
bcsource=blockchain_source, bcsource=blockchain_source,
network=network, network=network,
irc_configs=json.dumps(irc_configs), irc_configs=irc_configs,
minmakers=minmakers, minmakers=minmakers,
maker_timeout_sec=maker_timeout_sec, maker_timeout_sec=maker_timeout_sec,
dust_threshold=jm_single().DUST_THRESHOLD) dust_threshold=jm_single().DUST_THRESHOLD)
@ -443,10 +442,8 @@ class JMMakerClientProtocol(JMClientProtocol):
@commands.JMAuthReceived.responder @commands.JMAuthReceived.responder
def on_JM_AUTH_RECEIVED(self, nick, offer, commitment, revelation, amount, def on_JM_AUTH_RECEIVED(self, nick, offer, commitment, revelation, amount,
kphex): kphex):
offer = json.loads(offer)
revelation = json.loads(revelation)
retval = self.client.on_auth_received(nick, offer, retval = self.client.on_auth_received(nick, offer,
commitment, revelation, amount, kphex) commitment, revelation, amount, kphex)
if not retval[0]: if not retval[0]:
jlog.info("Maker refuses to continue on receiving auth.") jlog.info("Maker refuses to continue on receiving auth.")
else: else:
@ -461,7 +458,7 @@ class JMMakerClientProtocol(JMClientProtocol):
auth_pub_hex = bintohex(auth_pub) auth_pub_hex = bintohex(auth_pub)
d = self.callRemote(commands.JMIOAuth, d = self.callRemote(commands.JMIOAuth,
nick=nick, nick=nick,
utxolist=json.dumps(utxos_strkeyed), utxolist=utxos_strkeyed,
pubkey=auth_pub_hex, pubkey=auth_pub_hex,
cjaddr=cj_addr, cjaddr=cj_addr,
changeaddr=change_addr, changeaddr=change_addr,
@ -470,17 +467,15 @@ class JMMakerClientProtocol(JMClientProtocol):
return {"accepted": True} return {"accepted": True}
@commands.JMTXReceived.responder @commands.JMTXReceived.responder
def on_JM_TX_RECEIVED(self, nick, txhex, offer): def on_JM_TX_RECEIVED(self, nick, tx, offer):
offer = json.loads(offer) retval = self.client.on_tx_received(nick, tx, offer)
retval = self.client.on_tx_received(nick, txhex, offer)
if not retval[0]: if not retval[0]:
jlog.info("Maker refuses to continue on receipt of tx") jlog.info("Maker refuses to continue on receipt of tx")
else: else:
sigs = retval[1] sigs = retval[1]
self.finalized_offers[nick] = offer self.finalized_offers[nick] = offer
tx = btc.CMutableTransaction.deserialize(hextobin(txhex)) tx = btc.CMutableTransaction.deserialize(tx)
self.finalized_offers[nick]["txd"] = tx self.finalized_offers[nick]["txd"] = tx
txid = tx.GetTxid()[::-1]
# we index the callback by the out-set of the transaction, # we index the callback by the out-set of the transaction,
# because the txid is not known until all scriptSigs collected # because the txid is not known until all scriptSigs collected
# (hence this is required for Makers, but not Takers). # (hence this is required for Makers, but not Takers).
@ -497,14 +492,12 @@ class JMMakerClientProtocol(JMClientProtocol):
txinfo, self.unconfirm_callback, "unconfirmed", txinfo, self.unconfirm_callback, "unconfirmed",
"transaction with outputs: " + str(txinfo) + " not broadcast.") "transaction with outputs: " + str(txinfo) + " not broadcast.")
d = self.callRemote(commands.JMTXSigs, d = self.callRemote(commands.JMTXSigs, nick=nick, sigs=sigs)
nick=nick,
sigs=json.dumps(sigs))
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {"accepted": True} return {"accepted": True}
@commands.JMTXBroadcast.responder @commands.JMTXBroadcast.responder
def on_JM_TX_BROADCAST(self, txhex): def on_JM_TX_BROADCAST(self, tx):
""" Makers have no issue broadcasting anything, """ Makers have no issue broadcasting anything,
so only need to prevent crashes. so only need to prevent crashes.
Note in particular we don't check the return value, Note in particular we don't check the return value,
@ -512,15 +505,14 @@ class JMMakerClientProtocol(JMClientProtocol):
our (maker)'s concern. our (maker)'s concern.
""" """
try: try:
txbin = hextobin(txhex) jm_single().bc_interface.pushtx(tx)
jm_single().bc_interface.pushtx(txbin)
except: except:
jlog.info("We received an invalid transaction broadcast " jlog.info("We received an invalid transaction broadcast "
"request: " + txhex) "request: " + tx.hex())
return {"accepted": True} return {"accepted": True}
def tx_match(self, txd): def tx_match(self, txd):
for k,v in self.finalized_offers.items(): for k, v in self.finalized_offers.items():
# Tx considered defined by its output set # Tx considered defined by its output set
if v["txd"].vout == txd.vout: if v["txd"].vout == txd.vout:
offerinfo = v offerinfo = v
@ -547,9 +539,9 @@ class JMMakerClientProtocol(JMClientProtocol):
"transaction with outputs " + str(txinfo) + " not confirmed.") "transaction with outputs " + str(txinfo) + " not confirmed.")
d = self.callRemote(commands.JMAnnounceOffers, d = self.callRemote(commands.JMAnnounceOffers,
to_announce=json.dumps(to_announce), to_announce=to_announce,
to_cancel=json.dumps(to_cancel), to_cancel=to_cancel,
offerlist=json.dumps(self.client.offerlist)) offerlist=self.client.offerlist)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return True return True
@ -564,9 +556,9 @@ class JMMakerClientProtocol(JMClientProtocol):
txid, confirms) txid, confirms)
self.client.modify_orders(to_cancel, to_announce) self.client.modify_orders(to_cancel, to_announce)
d = self.callRemote(commands.JMAnnounceOffers, d = self.callRemote(commands.JMAnnounceOffers,
to_announce=json.dumps(to_announce), to_announce=to_announce,
to_cancel=json.dumps(to_cancel), to_cancel=to_cancel,
offerlist=json.dumps(self.client.offerlist)) offerlist=self.client.offerlist)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return True return True
@ -601,7 +593,7 @@ class JMTakerClientProtocol(JMClientProtocol):
d = self.callRemote(commands.JMInit, d = self.callRemote(commands.JMInit,
bcsource=blockchain_source, bcsource=blockchain_source,
network=network, network=network,
irc_configs=json.dumps(irc_configs), irc_configs=irc_configs,
minmakers=minmakers, minmakers=minmakers,
maker_timeout_sec=maker_timeout_sec, maker_timeout_sec=maker_timeout_sec,
dust_threshold=jm_single().DUST_THRESHOLD) dust_threshold=jm_single().DUST_THRESHOLD)
@ -641,7 +633,7 @@ class JMTakerClientProtocol(JMClientProtocol):
def on_JM_UP(self): def on_JM_UP(self):
d = self.callRemote(commands.JMSetup, d = self.callRemote(commands.JMSetup,
role="TAKER", role="TAKER",
offers="{}", initdata=None,
use_fidelity_bond=False) use_fidelity_bond=False)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -669,13 +661,12 @@ class JMTakerClientProtocol(JMClientProtocol):
the ioauth data and returns the proposed the ioauth data and returns the proposed
transaction, passes the phase 2 initiating data to the daemon. transaction, passes the phase 2 initiating data to the daemon.
""" """
ioauth_data = json.loads(ioauth_data)
if not success: if not success:
jlog.info("Makers who didnt respond: " + str(ioauth_data)) jlog.info("Makers who didnt respond: " + str(ioauth_data))
self.client.add_ignored_makers(ioauth_data) self.client.add_ignored_makers(ioauth_data)
return {'accepted': True} return {'accepted': True}
else: else:
jlog.info("Makers responded with: " + json.dumps(ioauth_data)) jlog.info("Makers responded with: " + str(ioauth_data))
retval = self.client.receive_utxos(ioauth_data) retval = self.client.receive_utxos(ioauth_data)
if not retval[0]: if not retval[0]:
jlog.info("Taker is not continuing, phase 2 abandoned.") jlog.info("Taker is not continuing, phase 2 abandoned.")
@ -686,8 +677,8 @@ class JMTakerClientProtocol(JMClientProtocol):
self.client.on_finished_callback(False, False, 0.0) self.client.on_finished_callback(False, False, 0.0)
return {'accepted': False} return {'accepted': False}
else: else:
nick_list, txhex = retval[1:] nick_list, tx = retval[1:]
reactor.callLater(0, self.make_tx, nick_list, txhex) reactor.callLater(0, self.make_tx, nick_list, tx)
return {'accepted': True} return {'accepted': True}
@commands.JMOffers.responder @commands.JMOffers.responder
@ -717,7 +708,7 @@ class JMTakerClientProtocol(JMClientProtocol):
amount=amt, amount=amt,
commitment=str(cmt), commitment=str(cmt),
revelation=str(rev), revelation=str(rev),
filled_offers=json.dumps(foffers)) filled_offers=foffers)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -725,17 +716,16 @@ class JMTakerClientProtocol(JMClientProtocol):
def on_JM_SIG_RECEIVED(self, nick, sig): def on_JM_SIG_RECEIVED(self, nick, sig):
retval = self.client.on_sig(nick, sig) retval = self.client.on_sig(nick, sig)
if retval: if retval:
nick_to_use, txhex = retval nick_to_use, tx = retval
self.push_tx(nick_to_use, txhex) self.push_tx(nick_to_use, tx)
return {'accepted': True} return {'accepted': True}
def get_offers(self): def get_offers(self):
d = self.callRemote(commands.JMRequestOffers) d = self.callRemote(commands.JMRequestOffers)
self.defaultCallbacks(d) self.defaultCallbacks(d)
def push_tx(self, nick_to_push, txhex_to_push): def push_tx(self, nick_to_push, tx):
d = self.callRemote(commands.JMPushTx, nick=str(nick_to_push), d = self.callRemote(commands.JMPushTx, nick=str(nick_to_push), tx=tx)
txhex=str(txhex_to_push))
self.defaultCallbacks(d) self.defaultCallbacks(d)
class SNICKERClientProtocolFactory(protocol.ClientFactory): class SNICKERClientProtocolFactory(protocol.ClientFactory):

6
jmclient/jmclient/maker.py

@ -118,7 +118,7 @@ class Maker(object):
return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig) return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig)
@hexbin @hexbin
def on_tx_received(self, nick, tx_from_taker, offerinfo): def on_tx_received(self, nick, tx, offerinfo):
"""Called when the counterparty has sent an unsigned """Called when the counterparty has sent an unsigned
transaction. Sigs are created and returned if and only transaction. Sigs are created and returned if and only
if the transaction passes verification checks (see if the transaction passes verification checks (see
@ -129,9 +129,9 @@ class Maker(object):
if not isinstance(offerinfo["offer"]["cjfee"], str): if not isinstance(offerinfo["offer"]["cjfee"], str):
offerinfo["offer"]["cjfee"] = bintohex(offerinfo["offer"]["cjfee"]) offerinfo["offer"]["cjfee"] = bintohex(offerinfo["offer"]["cjfee"])
try: try:
tx = btc.CMutableTransaction.deserialize(tx_from_taker) tx = btc.CMutableTransaction.deserialize(tx)
except Exception as e: except Exception as e:
return (False, 'malformed txhex. ' + repr(e)) return (False, 'malformed tx. ' + repr(e))
# if the above deserialization was successful, the human readable # if the above deserialization was successful, the human readable
# parsing will be also: # parsing will be also:
jlog.info('obtained tx\n' + btc.human_readable_transaction(tx)) jlog.info('obtained tx\n' + btc.human_readable_transaction(tx))

4
jmclient/jmclient/taker.py

@ -524,7 +524,7 @@ class Taker(object):
self.taker_info_callback("INFO", "Built tx, sending to counterparties.") self.taker_info_callback("INFO", "Built tx, sending to counterparties.")
return (True, list(self.maker_utxo_data.keys()), return (True, list(self.maker_utxo_data.keys()),
bintohex(self.latest_tx.serialize())) self.latest_tx.serialize())
def _verify_ioauth_data(self, ioauth_data): def _verify_ioauth_data(self, ioauth_data):
verified_data = [] verified_data = []
@ -943,7 +943,7 @@ class Taker(object):
self.on_finished_callback(False, fromtx=True) self.on_finished_callback(False, fromtx=True)
else: else:
if nick_to_use: if nick_to_use:
return (nick_to_use, bintohex(self.latest_tx.serialize())) return (nick_to_use, self.latest_tx.serialize())
#if push was not successful, return None #if push was not successful, return None
def self_sign_and_push(self): def self_sign_and_push(self):

65
jmclient/test/test_client_protocol.py

@ -20,6 +20,7 @@ from commontest import default_max_cj_fee
import json import json
import jmbitcoin as bitcoin import jmbitcoin as bitcoin
import twisted import twisted
import base64
twisted.internet.base.DelayedCall.debug = True twisted.internet.base.DelayedCall.debug = True
test_completed = False test_completed = False
@ -64,7 +65,8 @@ class DummyTaker(Taker):
if self.failutxos: if self.failutxos:
return (False, "dummyreason") return (False, "dummyreason")
else: else:
return (True, [x*64 + ":01" for x in ["a", "b", "c"]], t_raw_signed_tx) return (True, [x*64 + ":01" for x in ["a", "b", "c"]],
base64.b16decode(t_raw_signed_tx, casefold=True))
def on_sig(self, nick, sigb64): def on_sig(self, nick, sigb64):
@ -99,7 +101,7 @@ class DummyMaker(Maker):
# success, utxos, auth_pub, cj_addr, change_addr, btc_sig # success, utxos, auth_pub, cj_addr, change_addr, btc_sig
return True, [], b"", '', '', '' return True, [], b"", '', '', ''
def on_tx_received(self, nick, txhex, offerinfo): def on_tx_received(self, nick, tx, offerinfo):
# success, sigs # success, sigs
return True, [] return True, []
@ -185,8 +187,8 @@ class JMTestServerProtocol(JMBaseProtocol):
return {'accepted': True} return {'accepted': True}
@JMSetup.responder @JMSetup.responder
def on_JM_SETUP(self, role, offers, use_fidelity_bond): def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
show_receipt("JMSETUP", role, offers, use_fidelity_bond) show_receipt("JMSETUP", role, initdata, use_fidelity_bond)
d = self.callRemote(JMSetupDone) d = self.callRemote(JMSetupDone)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -198,8 +200,8 @@ class JMTestServerProtocol(JMBaseProtocol):
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(JMOffers, d = self.callRemote(JMOffers,
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}
@ -208,24 +210,24 @@ class JMTestServerProtocol(JMBaseProtocol):
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(JMFillResponse, d = self.callRemote(JMFillResponse,
success=success, success=success,
ioauth_data = json.dumps(['dummy', 'list'])) ioauth_data=['dummy', 'list'])
return {'accepted': True} return {'accepted': True}
@JMMakeTx.responder @JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, txhex): def on_JM_MAKE_TX(self, nick_list, tx):
show_receipt("JMMAKETX", nick_list, txhex) show_receipt("JMMAKETX", nick_list, tx)
d = self.callRemote(JMSigReceived, d = self.callRemote(JMSigReceived,
nick="dummynick", nick="dummynick",
sig="xxxsig") 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(JMRequestMsgSig, d2 = self.callRemote(JMRequestMsgSig,
nick="dummynickforsign", nick="dummynickforsign",
cmd="command1", cmd="command1",
msg="msgforsign", msg="msgforsign",
msg_to_be_signed="fullmsgforsign", msg_to_be_signed="fullmsgforsign",
hostid="hostid1") hostid="hostid1")
self.defaultCallbacks(d2) self.defaultCallbacks(d2)
#To test, this must include a valid ecdsa sig #To test, this must include a valid ecdsa sig
fullmsg = "fullmsgforverify" fullmsg = "fullmsgforverify"
@ -233,18 +235,18 @@ class JMTestServerProtocol(JMBaseProtocol):
pub = bintohex(bitcoin.privkey_to_pubkey(priv)) pub = bintohex(bitcoin.privkey_to_pubkey(priv))
sig = bitcoin.ecdsa_sign(fullmsg, priv) sig = bitcoin.ecdsa_sign(fullmsg, priv)
d3 = self.callRemote(JMRequestMsgSigVerify, d3 = self.callRemote(JMRequestMsgSigVerify,
msg="msgforverify", msg="msgforverify",
fullmsg=fullmsg, fullmsg=fullmsg,
sig=sig, sig=sig,
pubkey=pub, pubkey=pub,
nick="dummynickforverify", nick="dummynickforverify",
hashlen=4, hashlen=4,
max_encoded=5, max_encoded=5,
hostid="hostid2") hostid="hostid2")
self.defaultCallbacks(d3) self.defaultCallbacks(d3)
d4 = self.callRemote(JMSigReceived, d4 = self.callRemote(JMSigReceived,
nick="dummynick", nick="dummynick",
sig="dummysig") sig="dummysig")
self.defaultCallbacks(d4) self.defaultCallbacks(d4)
return {'accepted': True} return {'accepted': True}
@ -390,13 +392,14 @@ class TestMakerClientProtocol(unittest.TestCase):
def test_JMAuthReceived(self): def test_JMAuthReceived(self):
yield self.init_client() yield self.init_client()
yield self.callClient( yield self.callClient(
JMAuthReceived, nick='testnick', offer='{}', JMAuthReceived, nick='testnick', offer={},
commitment='testcommitment', revelation='{}', amount=100000, commitment='testcommitment', revelation={}, amount=100000,
kphex='testkphex') kphex='testkphex')
@inlineCallbacks @inlineCallbacks
def test_JMTXReceived(self): def test_JMTXReceived(self):
yield self.init_client() yield self.init_client()
yield self.callClient( yield self.callClient(
JMTXReceived, nick='testnick', txhex=t_raw_signed_tx, JMTXReceived, nick='testnick',
offer='{"cjaddr":"2MwfecDHsQTm4Gg3RekQdpqAMR15BJrjfRF"}') tx=base64.b16decode(t_raw_signed_tx, casefold=True),
offer={"cjaddr":"2MwfecDHsQTm4Gg3RekQdpqAMR15BJrjfRF"})

6
jmclient/test/test_coinjoin.py

@ -9,7 +9,7 @@ import pytest
import copy import copy
from twisted.internet import reactor from twisted.internet import reactor
from jmbase import get_log, hextobin from jmbase import get_log
from jmclient import load_test_config, jm_single,\ from jmclient import load_test_config, jm_single,\
YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet, SegwitWallet,\ YieldGeneratorBasic, Taker, LegacyWallet, SegwitLegacyWallet, SegwitWallet,\
NO_ROUNDING NO_ROUNDING
@ -206,7 +206,7 @@ def test_coinjoin_mixdepth_wrap_taker(monkeypatch, tmpdir, setup_cj):
taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) taker_final_result = do_tx_signing(taker, makers, active_orders, txdata)
assert taker_final_result is not False assert taker_final_result is not False
tx = btc.CMutableTransaction.deserialize(hextobin(txdata[2])) tx = btc.CMutableTransaction.deserialize(txdata[2])
wallet_service = wallet_services[-1] wallet_service = wallet_services[-1]
# TODO change for new tx monitoring: # TODO change for new tx monitoring:
@ -261,7 +261,7 @@ def test_coinjoin_mixdepth_wrap_maker(monkeypatch, tmpdir, setup_cj):
taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) taker_final_result = do_tx_signing(taker, makers, active_orders, txdata)
assert taker_final_result is not False assert taker_final_result is not False
tx = btc.CMutableTransaction.deserialize(hextobin(txdata[2])) tx = btc.CMutableTransaction.deserialize(txdata[2])
for i in range(MAKER_NUM): for i in range(MAKER_NUM):
wallet_service = wallet_services[i] wallet_service = wallet_services[i]

207
jmdaemon/jmdaemon/daemon_protocol.py

@ -9,7 +9,7 @@ from .protocol import (COMMAND_PREFIX, ORDER_KEYS, NICK_HASH_LENGTH,
COMMITMENT_PREFIXES) COMMITMENT_PREFIXES)
from .irc import IRCMessageChannel from .irc import IRCMessageChannel
from jmbase import (hextobin, is_hs_uri, get_tor_agent, JMHiddenService, from jmbase import (is_hs_uri, get_tor_agent, JMHiddenService,
get_nontor_agent, BytesProducer, wrapped_urlparse, get_nontor_agent, BytesProducer, wrapped_urlparse,
bdict_sdict_convert, JMHTTPResource) bdict_sdict_convert, JMHTTPResource)
from jmbase.commands import * from jmbase.commands import *
@ -32,7 +32,6 @@ import os
from io import BytesIO from io import BytesIO
import copy import copy
from functools import wraps from functools import wraps
from numbers import Integral
"""Joinmarket application protocol control flow. """Joinmarket application protocol control flow.
For documentation on protocol (formats, message sequence) see For documentation on protocol (formats, message sequence) see
@ -101,11 +100,11 @@ class BIP78ReceiverResource(JMHTTPResource):
def __init__(self, info_callback, shutdown_callback, post_request_handler): def __init__(self, info_callback, shutdown_callback, post_request_handler):
""" The POST request handling callback has function signature: """ The POST request handling callback has function signature:
args: (request-body-content-in-bytes,) args: (request-body-content-in-bytes,)
returns: (errormsg, errcode, httpcode, response-in-bytes) returns: (errormsg, errcode, httpcode, response-in-bytes)
If the request was successful, errormsg should be true and response If the request was successful, errormsg should be true and response
should be in bytes, to be sent in the return value of render_POST(). should be in bytes, to be sent in the return value of render_POST().
""" """
self.post_request_handler = post_request_handler self.post_request_handler = post_request_handler
super().__init__(info_callback, shutdown_callback) super().__init__(info_callback, shutdown_callback)
@ -158,6 +157,7 @@ class BIP78ReceiverResource(JMHTTPResource):
self.info_callback("Shutting down, payjoin negotiation failed.") self.info_callback("Shutting down, payjoin negotiation failed.")
self.shutdown_callback() self.shutdown_callback()
class HTTPPassThrough(amp.AMP): class HTTPPassThrough(amp.AMP):
""" This class supports passing through """ This class supports passing through
requests over HTTPS or over a socks proxy to a remote requests over HTTPS or over a socks proxy to a remote
@ -166,15 +166,14 @@ class HTTPPassThrough(amp.AMP):
def on_INIT(self, netconfig): def on_INIT(self, netconfig):
""" The network config must be passed in json """ The network config must be passed in json
and contains these fields: and contains these fields:
socks5_host socks5_host
socks5_proxy socks5_proxy
servers (comma separated list) servers (comma separated list)
tls_whitelist (comma separated list) tls_whitelist (comma separated list)
filterconfig (not yet defined) filterconfig (not yet defined)
credentials (not yet defined) credentials (not yet defined)
""" """
netconfig = json.loads(netconfig)
self.socks5_host = netconfig["socks5_host"] self.socks5_host = netconfig["socks5_host"]
self.socks5_port = int(netconfig["socks5_port"]) self.socks5_port = int(netconfig["socks5_port"])
self.servers = [a for a in netconfig["servers"] if a != ""] self.servers = [a for a in netconfig["servers"] if a != ""]
@ -206,9 +205,9 @@ class HTTPPassThrough(amp.AMP):
def getRequest(self, server, success_callback, url=None, headers=None): def getRequest(self, server, success_callback, url=None, headers=None):
""" Make GET request to server server, if response received OK, """ Make GET request to server server, if response received OK,
passed to success_callback, which must have function signature passed to success_callback, which must have function signature
(response, server). (response, server).
""" """
agent, destination_url = self.getAgentDestination(server) agent, destination_url = self.getAgentDestination(server)
if url: if url:
destination_url = destination_url + url destination_url = destination_url + url
@ -230,7 +229,7 @@ class HTTPPassThrough(amp.AMP):
def postRequest(self, body, server, success_callback, def postRequest(self, body, server, success_callback,
url=None, params=None, headers=None): url=None, params=None, headers=None):
""" Pass body of post request as string, will be encoded here. """ Pass body of post request as string, will be encoded here.
""" """
agent, destination_url = self.getAgentDestination(server, agent, destination_url = self.getAgentDestination(server,
params=params) params=params)
if url: if url:
@ -272,7 +271,6 @@ class HTTPPassThrough(amp.AMP):
class BIP78ServerProtocol(HTTPPassThrough): class BIP78ServerProtocol(HTTPPassThrough):
@BIP78ReceiverInit.responder @BIP78ReceiverInit.responder
def on_BIP78_RECEIVER_INIT(self, netconfig): def on_BIP78_RECEIVER_INIT(self, netconfig):
netconfig = json.loads(netconfig)
self.serving_port = int(netconfig["port"]) self.serving_port = int(netconfig["port"])
self.tor_control_host = netconfig["tor_control_host"] self.tor_control_host = netconfig["tor_control_host"]
self.tor_control_port = int(netconfig["tor_control_port"]) self.tor_control_port = int(netconfig["tor_control_port"])
@ -304,28 +302,28 @@ class BIP78ServerProtocol(HTTPPassThrough):
def info_callback(self, msg): def info_callback(self, msg):
""" Informational messages are all passed """ Informational messages are all passed
to the client. TODO makes sense to log locally to the client. TODO makes sense to log locally
too, in case daemon is isolated?. too, in case daemon is isolated?.
""" """
d = self.callRemote(BIP78InfoMsg, infomsg=msg) d = self.callRemote(BIP78InfoMsg, infomsg=msg)
self.defaultCallbacks(d) self.defaultCallbacks(d)
def onion_hostname_callback(self, hostname): def onion_hostname_callback(self, hostname):
""" On successful start of HS, we pass hostname """ On successful start of HS, we pass hostname
to client, who can use this to build the full URI. to client, who can use this to build the full URI.
""" """
d = self.callRemote(BIP78ReceiverUp, d = self.callRemote(BIP78ReceiverUp,
hostname=hostname) hostname=hostname)
self.defaultCallbacks(d) self.defaultCallbacks(d)
def post_request_handler(self, request, body, params): def post_request_handler(self, request, body, params):
""" Fired when a sender has sent a POST request """ Fired when a sender has sent a POST request
to our hidden service. Argument `body` should be a base64 to our hidden service. Argument `body` should be a base64
string and params should be a dict. string and params should be a dict.
""" """
self.post_request = request self.post_request = request
d = self.callRemote(BIP78ReceiverOriginalPSBT, body=body, d = self.callRemote(BIP78ReceiverOriginalPSBT, body=body,
params=json.dumps(bdict_sdict_convert(params))) params=bdict_sdict_convert(params))
self.defaultCallbacks(d) self.defaultCallbacks(d)
@BIP78ReceiverSendProposal.responder @BIP78ReceiverSendProposal.responder
@ -354,9 +352,9 @@ class BIP78ServerProtocol(HTTPPassThrough):
@BIP78SenderOriginalPSBT.responder @BIP78SenderOriginalPSBT.responder
def on_BIP78_SENDER_ORIGINAL_PSBT(self, body, params): def on_BIP78_SENDER_ORIGINAL_PSBT(self, body, params):
self.postRequest(body, self.servers[0], self.postRequest(body, self.servers[0],
self.bip78_receiver_response, self.bip78_receiver_response,
params=json.loads(params), params=params,
headers=Headers({"Content-Type": ["text/plain"]})) headers=Headers({"Content-Type": ["text/plain"]}))
return {"accepted": True} return {"accepted": True}
def bip78_receiver_response(self, response, server): def bip78_receiver_response(self, response, server):
@ -384,8 +382,8 @@ class SNICKERDaemonServerProtocol(HTTPPassThrough):
@SNICKERProposerPostProposals.responder @SNICKERProposerPostProposals.responder
def on_SNICKER_PROPOSER_POST_PROPOSALS(self, proposals, server): def on_SNICKER_PROPOSER_POST_PROPOSALS(self, proposals, server):
""" Receives a list of proposals to be posted to a specific """ Receives a list of proposals to be posted to a specific
server. server.
""" """
self.postRequest(proposals, server, self.receive_proposals_response) self.postRequest(proposals, server, self.receive_proposals_response)
return {"accepted": True} return {"accepted": True}
@ -421,7 +419,7 @@ class SNICKERDaemonServerProtocol(HTTPPassThrough):
def receive_proposals_from_server(self, response, server): def receive_proposals_from_server(self, response, server):
""" Parses the response from one server. """ Parses the response from one server.
""" """
# if the response code is not 200 OK, we must let the client # if the response code is not 200 OK, we must let the client
# know that this server is not responding as expected. # know that this server is not responding as expected.
if int(response.code) != 200: if int(response.code) != 200:
@ -475,6 +473,8 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
self.sig_lock = threading.Lock() self.sig_lock = threading.Lock()
self.active_orders = {} self.active_orders = {}
self.use_fidelity_bond = False self.use_fidelity_bond = False
self.offerlist = None
self.kp = None
def checkClientResponse(self, response): def checkClientResponse(self, response):
"""A generic check of client acceptance; any failure """A generic check of client acceptance; any failure
@ -485,7 +485,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
def defaultErrback(self, failure): def defaultErrback(self, failure):
"""TODO better network error handling. """TODO better network error handling.
""" """
failure.trap(ConnectionAborted, ConnectionClosed, failure.trap(ConnectionAborted, ConnectionClosed,
ConnectionDone, ConnectionLost) ConnectionDone, ConnectionLost)
@ -502,11 +502,9 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
If a new message channel configuration is required, the current If a new message channel configuration is required, the current
one is shutdown in preparation. one is shutdown in preparation.
""" """
self.maker_timeout_sec = int(maker_timeout_sec) self.maker_timeout_sec = maker_timeout_sec
# used in OrderbookWatch: self.minmakers = minmakers
self.dust_threshold = int(dust_threshold) self.dust_threshold = int(dust_threshold)
self.minmakers = int(minmakers)
irc_configs = json.loads(irc_configs)
#(bitcoin) network only referenced in channel name construction #(bitcoin) network only referenced in channel name construction
self.network = network self.network = network
if irc_configs == self.irc_configs: if irc_configs == self.irc_configs:
@ -554,7 +552,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
return {'accepted': True} return {'accepted': True}
@JMSetup.responder @JMSetup.responder
def on_JM_SETUP(self, role, offers, use_fidelity_bond): def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
assert self.jm_state == 0 assert self.jm_state == 0
self.role = role self.role = role
self.crypto_boxes = {} self.crypto_boxes = {}
@ -568,7 +566,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
if self.role == "TAKER": if self.role == "TAKER":
self.mcc.pubmsg(COMMAND_PREFIX + "orderbook") self.mcc.pubmsg(COMMAND_PREFIX + "orderbook")
elif self.role == "MAKER": elif self.role == "MAKER":
self.offerlist = json.loads(offers) self.offerlist = initdata
self.use_fidelity_bond = use_fidelity_bond self.use_fidelity_bond = use_fidelity_bond
self.mcc.announce_orders(self.offerlist, None, None, None) self.mcc.announce_orders(self.offerlist, None, None, None)
self.jm_state = 1 self.jm_state = 1
@ -614,15 +612,14 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
"""Takes the necessary data from the Taker and initiates the Stage 1 """Takes the necessary data from the Taker and initiates the Stage 1
interaction with the Makers. interaction with the Makers.
""" """
if not (self.jm_state == 1 and isinstance(amount, Integral) if self.jm_state != 1 or amount < 0:
and amount >= 0):
return {'accepted': False} return {'accepted': False}
self.cjamount = amount self.cjamount = amount
self.commitment = commitment self.commitment = commitment
self.revelation = revelation self.revelation = revelation
#Reset utxo data to null for this new transaction #Reset utxo data to null for this new transaction
self.ioauth_data = {} self.ioauth_data = {}
self.active_orders = json.loads(filled_offers) self.active_orders = filled_offers
for nick, offer_dict in self.active_orders.items(): for nick, offer_dict in self.active_orders.items():
offer_fill_msg = " ".join([str(offer_dict["oid"]), str(amount), offer_fill_msg = " ".join([str(offer_dict["oid"]), str(amount),
self.kp.hex_pk().decode('ascii'), str(commitment)]) self.kp.hex_pk().decode('ascii'), str(commitment)])
@ -632,20 +629,19 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
return {'accepted': True} return {'accepted': True}
@JMMakeTx.responder @JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, txhex): def on_JM_MAKE_TX(self, nick_list, tx):
"""Taker sends the prepared unsigned transaction """Taker sends the prepared unsigned transaction
to all the Makers in nick_list to all the Makers in nick_list
""" """
if not self.jm_state == 4: if not self.jm_state == 4:
log.msg("Make tx was called in wrong state, rejecting") log.msg("Make tx was called in wrong state, rejecting")
return {'accepted': False} return {'accepted': False}
nick_list = json.loads(nick_list) self.mcc.send_tx(nick_list, tx)
self.mcc.send_tx(nick_list, txhex)
return {'accepted': True} return {'accepted': True}
@JMPushTx.responder @JMPushTx.responder
def on_JM_PushTx(self, nick, txhex): def on_JM_PushTx(self, nick, tx):
self.mcc.push_tx(nick, txhex) self.mcc.push_tx(nick, tx)
return {'accepted': True} return {'accepted': True}
"""Maker specific responders """Maker specific responders
@ -654,14 +650,12 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@JMAnnounceOffers.responder @JMAnnounceOffers.responder
def on_JM_ANNOUNCE_OFFERS(self, to_announce, to_cancel, offerlist): def on_JM_ANNOUNCE_OFFERS(self, to_announce, to_cancel, offerlist):
"""Called by Maker to reset his current offerlist; """Called by Maker to reset his current offerlist;
Daemon decides what messages (cancel, announce) to Daemon decides what messages (cancel, announce) to
send to the message channel. send to the message channel.
""" """
if self.role != "MAKER": if self.role != "MAKER":
return return
to_announce = json.loads(to_announce) self.offerlist = offerlist
to_cancel = json.loads(to_cancel)
self.offerlist = json.loads(offerlist)
if len(to_cancel) > 0: if len(to_cancel) > 0:
self.mcc.cancel_orders(to_cancel) self.mcc.cancel_orders(to_cancel)
if len(to_announce) > 0: if len(to_announce) > 0:
@ -677,19 +671,18 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@JMIOAuth.responder @JMIOAuth.responder
def on_JM_IOAUTH(self, nick, utxolist, pubkey, cjaddr, changeaddr, pubkeysig): def on_JM_IOAUTH(self, nick, utxolist, pubkey, cjaddr, changeaddr, pubkeysig):
"""Daemon constructs full !ioauth message to be sent on message """Daemon constructs full !ioauth message to be sent on message
channel based on data from Maker. Relevant data (utxos, addresses) channel based on data from Maker. Relevant data (utxos, addresses)
are stored in the active_orders dict keyed by the nick of the Taker. are stored in the active_orders dict keyed by the nick of the Taker.
""" """
if not self.role == "MAKER": if not self.role == "MAKER":
return return
if not nick in self.active_orders: if nick not in self.active_orders:
return return
utxos= json.loads(utxolist)
#completed population of order/offer object #completed population of order/offer object
self.active_orders[nick]["cjaddr"] = cjaddr self.active_orders[nick]["cjaddr"] = cjaddr
self.active_orders[nick]["changeaddr"] = changeaddr self.active_orders[nick]["changeaddr"] = changeaddr
self.active_orders[nick]["utxos"] = utxos self.active_orders[nick]["utxos"] = utxolist
msg = str(",".join(utxos.keys())) + " " + " ".join( msg = str(",".join(utxolist)) + " " + " ".join(
[pubkey, cjaddr, changeaddr, pubkeysig]) [pubkey, cjaddr, changeaddr, pubkeysig])
self.mcc.prepare_privmsg(nick, "ioauth", msg) self.mcc.prepare_privmsg(nick, "ioauth", msg)
#In case of *blacklisted (ie already used) commitments, we already #In case of *blacklisted (ie already used) commitments, we already
@ -705,11 +698,10 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@JMTXSigs.responder @JMTXSigs.responder
def on_JM_TX_SIGS(self, nick, sigs): def on_JM_TX_SIGS(self, nick, sigs):
"""Signatures that the Maker has produced """Signatures that the Maker has produced
are passed here to the daemon as a list and are passed here to the daemon as a list and
broadcast one by one. TODO: could shorten this, broadcast one by one. TODO: could shorten this,
have more than one sig per message. have more than one sig per message.
""" """
sigs = json.loads(sigs)
for sig in sigs: for sig in sigs:
self.mcc.prepare_privmsg(nick, "sig", sig) self.mcc.prepare_privmsg(nick, "sig", sig)
return {"accepted": True} return {"accepted": True}
@ -794,19 +786,19 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@maker_only @maker_only
def on_seen_auth(self, nick, commitment_revelation): def on_seen_auth(self, nick, commitment_revelation):
"""Passes to Maker the !auth message from the Taker, """Passes to Maker the !auth message from the Taker,
for processing. This will include validating the PoDLE for processing. This will include validating the PoDLE
commitment revelation against the existing commitment, commitment revelation against the existing commitment,
which was already stored in active_orders[nick]. which was already stored in active_orders[nick].
""" """
if not nick in self.active_orders: if nick not in self.active_orders:
return return
ao =self.active_orders[nick] ao = self.active_orders[nick]
#ask the client to validate the commitment and prepare the utxo data #ask the client to validate the commitment and prepare the utxo data
d = self.callRemote(JMAuthReceived, d = self.callRemote(JMAuthReceived,
nick=nick, nick=nick,
offer=json.dumps(ao["offer"]), offer=ao["offer"],
commitment=ao["commit"], commitment=ao["commit"],
revelation=json.dumps(commitment_revelation), revelation=commitment_revelation,
amount=ao["amount"], amount=ao["amount"],
kphex=ao["kp"].hex_pk().decode('ascii')) kphex=ao["kp"].hex_pk().decode('ascii'))
self.defaultCallbacks(d) self.defaultCallbacks(d)
@ -814,8 +806,8 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@maker_only @maker_only
def on_commitment_seen(self, nick, commitment): def on_commitment_seen(self, nick, commitment):
"""Triggered when we see a commitment for blacklisting """Triggered when we see a commitment for blacklisting
appear in the public pit channel. appear in the public pit channel.
""" """
#just add if necessary, ignore return value. #just add if necessary, ignore return value.
check_utxo_blacklist(commitment, persist=True) check_utxo_blacklist(commitment, persist=True)
log.msg("Received commitment broadcast by other maker: " + str( log.msg("Received commitment broadcast by other maker: " + str(
@ -824,30 +816,24 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
@maker_only @maker_only
def on_commitment_transferred(self, nick, commitment): def on_commitment_transferred(self, nick, commitment):
"""Triggered when a privmsg is received from another maker """Triggered when a privmsg is received from another maker
with a commitment to announce in public (obfuscation of source). with a commitment to announce in public (obfuscation of source).
We simply post it in public (not affected by whether we ourselves We simply post it in public (not affected by whether we ourselves
are *accepting* commitment broadcasts. are *accepting* commitment broadcasts.
""" """
self.mcc.pubmsg("!hp2 " + commitment) self.mcc.pubmsg("!hp2 " + commitment)
@maker_only @maker_only
def on_push_tx(self, nick, txhex): def on_push_tx(self, nick, tx):
"""Broadcast unquestioningly, except checking """Broadcast unquestioningly
hex format. """
""" d = self.callRemote(JMTXBroadcast, tx=tx)
try:
dummy = hextobin(txhex)
except:
return
d = self.callRemote(JMTXBroadcast,
txhex=txhex)
self.defaultCallbacks(d) self.defaultCallbacks(d)
@maker_only @maker_only
def on_seen_tx(self, nick, txhex): def on_seen_tx(self, nick, tx):
"""Passes the txhex to the Maker for verification """Passes the txhex to the Maker for verification
and signing. Note the security checks occur in Maker. and signing. Note the security checks occur in Maker.
""" """
if nick not in self.active_orders: if nick not in self.active_orders:
return return
#we send a copy of the entire "active_orders" entry except the cryptobox, #we send a copy of the entire "active_orders" entry except the cryptobox,
@ -855,10 +841,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
ao = copy.deepcopy(self.active_orders[nick]) ao = copy.deepcopy(self.active_orders[nick])
del ao["crypto_box"] del ao["crypto_box"]
del ao["kp"] del ao["kp"]
d = self.callRemote(JMTXReceived, d = self.callRemote(JMTXReceived, nick=nick, tx=tx, offer=ao)
nick=nick,
txhex=txhex,
offer=json.dumps(ao))
self.defaultCallbacks(d) self.defaultCallbacks(d)
@taker_only @taker_only
@ -898,9 +881,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
def on_sig(self, nick, sig): def on_sig(self, nick, sig):
"""Pass signature through to Taker. """Pass signature through to Taker.
""" """
d = self.callRemote(JMSigReceived, d = self.callRemote(JMSigReceived, nick=nick, sig=sig)
nick=nick,
sig=sig)
self.defaultCallbacks(d) self.defaultCallbacks(d)
def on_error(self, msg): def on_error(self, msg):
@ -920,11 +901,11 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
""" """
with self.sig_lock: with self.sig_lock:
d = self.callRemote(JMRequestMsgSig, d = self.callRemote(JMRequestMsgSig,
nick=str(nick), nick=str(nick),
cmd=str(cmd), cmd=str(cmd),
msg=str(msg), msg=str(msg),
msg_to_be_signed=str(msg_to_be_signed), msg_to_be_signed=str(msg_to_be_signed),
hostid=str(hostid)) hostid=str(hostid))
self.defaultCallbacks(d) self.defaultCallbacks(d)
def request_signature_verify(self, msg, fullmsg, sig, pubkey, nick, hashlen, def request_signature_verify(self, msg, fullmsg, sig, pubkey, nick, hashlen,
@ -959,8 +940,8 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
def transfer_commitment(self, commit): def transfer_commitment(self, commit):
"""Send this commitment via privmsg to one (random) """Send this commitment via privmsg to one (random)
other maker. other maker.
""" """
crow = self.db.execute( crow = self.db.execute(
'SELECT DISTINCT counterparty FROM orderbook ORDER BY ' + 'SELECT DISTINCT counterparty FROM orderbook ORDER BY ' +
'RANDOM() LIMIT 1;' 'RANDOM() LIMIT 1;'
@ -990,12 +971,12 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
self.jm_state = 3 self.jm_state = 3
if not accepted: if not accepted:
#use ioauth data field to return the list of non-responsive makers #use ioauth data field to return the list of non-responsive makers
nonresponders = [x for x in self.active_orders.keys() if x not nonresponders = [x for x in self.active_orders
in self.ioauth_data.keys()] if x not in self.ioauth_data]
ioauth_data = self.ioauth_data if accepted else nonresponders ioauth_data = self.ioauth_data if accepted else nonresponders
d = self.callRemote(JMFillResponse, d = self.callRemote(JMFillResponse,
success=accepted, success=accepted,
ioauth_data = json.dumps(ioauth_data)) ioauth_data=ioauth_data)
if not accepted: if not accepted:
#Client simply accepts failure TODO #Client simply accepts failure TODO
self.defaultCallbacks(d) self.defaultCallbacks(d)
@ -1010,7 +991,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
either send success + ioauth data if enough makers, either send success + ioauth data if enough makers,
else send failure to client. else send failure to client.
""" """
response = True if len(self.ioauth_data.keys()) >= self.minmakers else False response = True if len(self.ioauth_data) >= self.minmakers else False
self.respondToIoauths(response) self.respondToIoauths(response)
def checkUtxosAccepted(self, accepted): def checkUtxosAccepted(self, accepted):

25
jmdaemon/jmdaemon/message_channel.py

@ -1,7 +1,6 @@
#! /usr/bin/env python #! /usr/bin/env python
import abc import abc
import base64 import base64
import binascii
import threading import threading
from twisted.internet import reactor from twisted.internet import reactor
from jmdaemon import encrypt_encode, decode_decrypt, COMMAND_PREFIX,\ from jmdaemon import encrypt_encode, decode_decrypt, COMMAND_PREFIX,\
@ -351,14 +350,14 @@ class MessageChannelCollection(object):
self.prepare_privmsg(nick, "error", errormsg) self.prepare_privmsg(nick, "error", errormsg)
@check_privmsg @check_privmsg
def push_tx(self, nick, txhex): def push_tx(self, nick, tx):
#TODO supporting sending to arbitrary nicks #TODO supporting sending to arbitrary nicks
#adds quite a bit of complexity, not supported #adds quite a bit of complexity, not supported
#initially; will fail if nick is not part of TX #initially; will fail if nick is not part of TX
txb64 = base64.b64encode(binascii.unhexlify(txhex)).decode('ascii') txb64 = base64.b64encode(tx).decode('ascii')
self.prepare_privmsg(nick, "push", txb64) self.prepare_privmsg(nick, "push", txb64)
def send_tx(self, nick_list, txhex): def send_tx(self, nick_list, tx):
"""Push out the transaction to nicks """Push out the transaction to nicks
in groups by their message channel. in groups by their message channel.
""" """
@ -376,10 +375,10 @@ class MessageChannelCollection(object):
else: else:
tx_nick_sets[self.active_channels[nick]].append(nick) tx_nick_sets[self.active_channels[nick]].append(nick)
for mc, nl in tx_nick_sets.items(): for mc, nl in tx_nick_sets.items():
self.prepare_send_tx(mc, nl, txhex) self.prepare_send_tx(mc, nl, tx)
def prepare_send_tx(self, mc, nick_list, txhex): def prepare_send_tx(self, mc, nick_list, tx):
txb64 = base64.b64encode(binascii.unhexlify(txhex)).decode('ascii') txb64 = base64.b64encode(tx).decode('ascii')
for nick in nick_list: for nick in nick_list:
self.prepare_privmsg(nick, "tx", txb64, mc=mc) self.prepare_privmsg(nick, "tx", txb64, mc=mc)
@ -853,10 +852,10 @@ class MessageChannel(object):
msg += ' ' + commitment msg += ' ' + commitment
self.privmsg(c, 'fill', msg) self.privmsg(c, 'fill', msg)
def push_tx(self, nick, txhex): def push_tx(self, nick, tx):
#Note: not currently used; will require prepare_privmsg call so #Note: not currently used; will require prepare_privmsg call so
#not in this class (see send_error) #not in this class (see send_error)
txb64 = base64.b64encode(binascii.unhexlify(txhex)).decode('ascii') txb64 = base64.b64encode(tx).decode('ascii')
self.privmsg(nick, 'push', txb64) self.privmsg(nick, 'push', txb64)
def send_error(self, nick, errormsg): def send_error(self, nick, errormsg):
@ -1028,21 +1027,21 @@ class MessageChannel(object):
elif _chunks[0] == 'tx': elif _chunks[0] == 'tx':
b64tx = _chunks[1] b64tx = _chunks[1]
try: try:
txhex = binascii.hexlify(base64.b64decode(b64tx)).decode('ascii') tx = base64.b64decode(b64tx)
except TypeError as e: except TypeError as e:
self.send_error(nick, 'bad base64 tx. ' + repr(e)) self.send_error(nick, 'bad base64 tx. ' + repr(e))
return return
if self.on_seen_tx: if self.on_seen_tx:
self.on_seen_tx(nick, txhex) self.on_seen_tx(nick, tx)
elif _chunks[0] == 'push': elif _chunks[0] == 'push':
b64tx = _chunks[1] b64tx = _chunks[1]
try: try:
txhex = binascii.hexlify(base64.b64decode(b64tx)).decode('ascii') tx = base64.b64decode(b64tx)
except TypeError as e: except TypeError as e:
self.send_error(nick, 'bad base64 tx. ' + repr(e)) self.send_error(nick, 'bad base64 tx. ' + repr(e))
return return
if self.on_push_tx: if self.on_push_tx:
self.on_push_tx(nick, txhex) self.on_push_tx(nick, tx)
except (IndexError, ValueError): except (IndexError, ValueError):
# TODO proper error handling # TODO proper error handling
log.debug('cj peer error TODO handle') log.debug('cj peer error TODO handle')

57
jmdaemon/test/test_daemon_protocol.py

@ -19,7 +19,6 @@ from twisted.protocols import amp
from twisted.trial import unittest from twisted.trial import unittest
from jmbase.commands import * from jmbase.commands import *
from msgdata import * from msgdata import *
import json
import base64 import base64
import sys import sys
from dummy_mc import DummyMessageChannel from dummy_mc import DummyMessageChannel
@ -64,7 +63,7 @@ class JMTestClientProtocol(JMBaseProtocol):
d = self.callRemote(JMInit, d = self.callRemote(JMInit,
bcsource="dummyblockchain", bcsource="dummyblockchain",
network="dummynetwork", network="dummynetwork",
irc_configs=json.dumps(irc), irc_configs=irc,
minmakers=2, minmakers=2,
maker_timeout_sec=3, maker_timeout_sec=3,
dust_threshold=27300) dust_threshold=27300)
@ -85,7 +84,7 @@ class JMTestClientProtocol(JMBaseProtocol):
show_receipt("JMUP") show_receipt("JMUP")
d = self.callRemote(JMSetup, d = self.callRemote(JMSetup,
role="TAKER", role="TAKER",
offers="{}", initdata=None,
use_fidelity_bond=False) use_fidelity_bond=False)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -104,11 +103,10 @@ class JMTestClientProtocol(JMBaseProtocol):
return {'accepted': True} return {'accepted': True}
def maketx(self, ioauth_data): def maketx(self, ioauth_data):
ioauth_data = json.loads(ioauth_data) nl = list(ioauth_data)
nl = list(ioauth_data.keys())
d = self.callRemote(JMMakeTx, d = self.callRemote(JMMakeTx,
nick_list= json.dumps(nl), nick_list=nl,
txhex="deadbeef") tx=b"deadbeef")
self.defaultCallbacks(d) self.defaultCallbacks(d)
@JMOffers.responder @JMOffers.responder
@ -120,23 +118,23 @@ class JMTestClientProtocol(JMBaseProtocol):
nick = str(list(t_chosen_orders.keys())[0]) nick = str(list(t_chosen_orders.keys())[0])
b64tx = base64.b64encode(b"deadbeef").decode('ascii') b64tx = base64.b64encode(b"deadbeef").decode('ascii')
d1 = self.callRemote(JMMsgSignatureVerify, d1 = self.callRemote(JMMsgSignatureVerify,
verif_result=True, verif_result=True,
nick=nick, nick=nick,
fullmsg="!push " + b64tx + " abc def", fullmsg="!push " + b64tx + " abc def",
hostid="dummy") hostid="dummy")
self.defaultCallbacks(d1) self.defaultCallbacks(d1)
#unverified #unverified
d2 = self.callRemote(JMMsgSignatureVerify, d2 = self.callRemote(JMMsgSignatureVerify,
verif_result=False, verif_result=False,
nick=nick, nick=nick,
fullmsg="!push " + b64tx + " abc def", fullmsg="!push " + b64tx + " abc def",
hostid="dummy") hostid="dummy")
self.defaultCallbacks(d2) self.defaultCallbacks(d2)
d = self.callRemote(JMFill, d = self.callRemote(JMFill,
amount=100, amount=100,
commitment="dummycommitment", commitment="dummycommitment",
revelation="dummyrevelation", revelation="dummyrevelation",
filled_offers=json.dumps(t_chosen_orders)) filled_offers=t_chosen_orders)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@ -215,9 +213,9 @@ class JMDaemonTestServerProtocol(JMDaemonServerProtocol):
@JMInit.responder @JMInit.responder
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, def on_JM_INIT(self, bcsource, network, irc_configs, minmakers,
maker_timeout_sec, dust_threshold): maker_timeout_sec, dust_threshold):
self.maker_timeout_sec = int(maker_timeout_sec) self.maker_timeout_sec = maker_timeout_sec
self.dust_threshold = int(dust_threshold) self.dust_threshold = int(dust_threshold)
self.minmakers = int(minmakers) self.minmakers = minmakers
mcs = [DummyMC(None)] mcs = [DummyMC(None)]
self.mcc = MessageChannelCollection(mcs) self.mcc = MessageChannelCollection(mcs)
#The following is a hack to get the counterparties marked seen/active; #The following is a hack to get the counterparties marked seen/active;
@ -228,43 +226,42 @@ class JMDaemonTestServerProtocol(JMDaemonServerProtocol):
OrderbookWatch.set_msgchan(self, self.mcc) OrderbookWatch.set_msgchan(self, self.mcc)
#register taker-specific msgchan callbacks here #register taker-specific msgchan callbacks here
self.mcc.register_taker_callbacks(self.on_error, self.on_pubkey, self.mcc.register_taker_callbacks(self.on_error, self.on_pubkey,
self.on_ioauth, self.on_sig) self.on_ioauth, self.on_sig)
self.mcc.set_daemon(self) self.mcc.set_daemon(self)
self.restart_mc_required = True self.restart_mc_required = True
d = self.callRemote(JMInitProto, d = self.callRemote(JMInitProto,
nick_hash_length=NICK_HASH_LENGTH, nick_hash_length=NICK_HASH_LENGTH,
nick_max_encoded=NICK_MAX_ENCODED, nick_max_encoded=NICK_MAX_ENCODED,
joinmarket_nick_header=JOINMARKET_NICK_HEADER, joinmarket_nick_header=JOINMARKET_NICK_HEADER,
joinmarket_version=JM_VERSION) joinmarket_version=JM_VERSION)
self.defaultCallbacks(d) self.defaultCallbacks(d)
return {'accepted': True} return {'accepted': True}
@JMFill.responder @JMFill.responder
def on_JM_FILL(self, amount, commitment, revelation, filled_offers): def on_JM_FILL(self, amount, commitment, revelation, filled_offers):
tmpfo = json.loads(filled_offers)
dummypub = "073732a7ca60470f709f23c602b2b8a6b1ba62ee8f3f83a61e5484ab5cbf9c3d" dummypub = "073732a7ca60470f709f23c602b2b8a6b1ba62ee8f3f83a61e5484ab5cbf9c3d"
#trigger invalid on_pubkey conditions #trigger invalid on_pubkey conditions
reactor.callLater(1, self.on_pubkey, "notrealcp", dummypub) reactor.callLater(1, self.on_pubkey, "notrealcp", dummypub)
reactor.callLater(2, self.on_pubkey, list(tmpfo.keys())[0], dummypub + "deadbeef") reactor.callLater(2, self.on_pubkey, list(filled_offers)[0], dummypub + "deadbeef")
#trigger invalid on_ioauth condition #trigger invalid on_ioauth condition
reactor.callLater(2, self.on_ioauth, "notrealcp", 1, 2, 3, 4, 5) reactor.callLater(2, self.on_ioauth, "notrealcp", 1, 2, 3, 4, 5)
#trigger msg sig verify request operation for a dummy message #trigger msg sig verify request operation for a dummy message
#currently a pass-through #currently a pass-through
reactor.callLater(1, self.request_signature_verify, "1", reactor.callLater(1, self.request_signature_verify, "1",
"!push abcd abc def", "3", "4", "!push abcd abc def", "3", "4",
str(list(tmpfo.keys())[0]), 6, 7, self.mcc.mchannels[0].hostid) str(list(filled_offers)[0]), 6, 7, self.mcc.mchannels[0].hostid)
#send "valid" onpubkey, onioauth messages #send "valid" onpubkey, onioauth messages
for k, v in tmpfo.items(): for k, v in filled_offers.items():
reactor.callLater(1, self.on_pubkey, k, dummypub) reactor.callLater(1, self.on_pubkey, k, dummypub)
reactor.callLater(2, self.on_ioauth, k, ['a', 'b'], "auth_pub", reactor.callLater(2, self.on_ioauth, k, ['a', 'b'], "auth_pub",
"cj_addr", "change_addr", "btc_sig") "cj_addr", "change_addr", "btc_sig")
return super().on_JM_FILL(amount, commitment, revelation, filled_offers) return super().on_JM_FILL(amount, commitment, revelation, filled_offers)
@JMMakeTx.responder @JMMakeTx.responder
def on_JM_MAKE_TX(self, nick_list, txhex): def on_JM_MAKE_TX(self, nick_list, tx):
for n in json.loads(nick_list): for n in nick_list:
reactor.callLater(1, self.on_sig, n, "dummytxsig") reactor.callLater(1, self.on_sig, n, "dummytxsig")
return super().on_JM_MAKE_TX(nick_list, txhex) return super().on_JM_MAKE_TX(nick_list, tx)

8
jmdaemon/test/test_message_channel.py

@ -160,7 +160,7 @@ def test_setup_mc():
mcc.privmsg(cp1+"XXX", "fill", "0") mcc.privmsg(cp1+"XXX", "fill", "0")
#trigger check_privmsg decorator #trigger check_privmsg decorator
mcc.send_error(cp1, "errormsg") mcc.send_error(cp1, "errormsg")
mcc.push_tx(cp1, "deadbeef") mcc.push_tx(cp1, b"deadbeef")
#kill the chan on which the cp is marked active; #kill the chan on which the cp is marked active;
#note dummychannel has no actual shutdown (call it anyway), #note dummychannel has no actual shutdown (call it anyway),
#so change its status manually. #so change its status manually.
@ -215,13 +215,13 @@ def test_setup_mc():
mcc.fill_orders(new_offers, 1000, "dummypubkey", "dummycommit") mcc.fill_orders(new_offers, 1000, "dummypubkey", "dummycommit")
#now send a dummy transaction to this same set. #now send a dummy transaction to this same set.
#first fails with no crypto box. #first fails with no crypto box.
mcc.send_tx(cps, "deadbeef") mcc.send_tx(cps, b"deadbeef")
#Now initialize the boxes #Now initialize the boxes
for c in cps: for c in cps:
dummydaemon.crypto_boxes[c] = ["a", DummyBox()] dummydaemon.crypto_boxes[c] = ["a", DummyBox()]
mcc.send_tx(cps, "deadbeef") mcc.send_tx(cps, b"deadbeef")
#try to send the transaction to a wrong cp: #try to send the transaction to a wrong cp:
mcc.send_tx(["notrealcp"], "deadbeef") mcc.send_tx(["notrealcp"], b"deadbeef")
#At this stage, dmcs0,2 should be "up" and 1 should be "down": #At this stage, dmcs0,2 should be "up" and 1 should be "down":
assert mcc.mc_status[dmcs[0]] == 1 assert mcc.mc_status[dmcs[0]] == 1

8
test/ygrunner.py

@ -54,12 +54,12 @@ class MaliciousYieldGenerator(YieldGeneratorBasic):
jmprint("Counterparty commitment rejected maliciously", "debug") jmprint("Counterparty commitment rejected maliciously", "debug")
return (False,) return (False,)
return super().on_auth_received(nick, offer, commitment, cr, amount, kphex) return super().on_auth_received(nick, offer, commitment, cr, amount, kphex)
def on_tx_received(self, nick, txhex, offerinfo): def on_tx_received(self, nick, tx, offerinfo):
if self.txmal: if self.txmal:
if random.randint(1, 100) < self.mfrac: if random.randint(1, 100) < self.mfrac:
jmprint("Counterparty tx rejected maliciously", "debug") jmprint("Counterparty tx rejected maliciously", "debug")
return (False, "malicious tx rejection") return (False, "malicious tx rejection")
return super().on_tx_received(nick, txhex, offerinfo) return super().on_tx_received(nick, tx, offerinfo)
class DeterministicMaliciousYieldGenerator(YieldGeneratorBasic): class DeterministicMaliciousYieldGenerator(YieldGeneratorBasic):
"""Overrides, randomly chosen persistently, some maker functions """Overrides, randomly chosen persistently, some maker functions
@ -85,11 +85,11 @@ class DeterministicMaliciousYieldGenerator(YieldGeneratorBasic):
jmprint("Counterparty commitment rejected maliciously", "debug") jmprint("Counterparty commitment rejected maliciously", "debug")
return (False,) return (False,)
return super().on_auth_received(nick, offer, commitment, cr, amount, kphex) return super().on_auth_received(nick, offer, commitment, cr, amount, kphex)
def on_tx_received(self, nick, txhex, offerinfo): def on_tx_received(self, nick, tx, offerinfo):
if self.txmal: if self.txmal:
jmprint("Counterparty tx rejected maliciously", "debug") jmprint("Counterparty tx rejected maliciously", "debug")
return (False, "malicious tx rejection") return (False, "malicious tx rejection")
return super().on_tx_received(nick, txhex, offerinfo) return super().on_tx_received(nick, tx, offerinfo)
@pytest.mark.parametrize( @pytest.mark.parametrize(

Loading…
Cancel
Save