diff --git a/jmclient/__init__.py b/jmclient/__init__.py index 6d39662..bdca501 100644 --- a/jmclient/__init__.py +++ b/jmclient/__init__.py @@ -27,7 +27,6 @@ from .podle import (set_commitment_file, get_commitment_file, generate_podle_error_string, add_external_commitments, PoDLE, generate_podle, get_podle_commitments, update_commitments) -from .commands import * from .schedule import get_schedule from .commitment_utils import get_utxo_info, validate_utxo_data, quit # Set default logging handler to avoid "No handler found" warnings. diff --git a/jmclient/client_protocol.py b/jmclient/client_protocol.py index 3bdb779..9e555a8 100644 --- a/jmclient/client_protocol.py +++ b/jmclient/client_protocol.py @@ -9,7 +9,7 @@ from twisted.python import failure from twisted.protocols import amp from twisted.internet.protocol import ClientFactory from twisted.internet.endpoints import TCP4ClientEndpoint -import commands +from jmbase import commands from sys import stdout import json diff --git a/jmdaemon/__init__.py b/jmdaemon/__init__.py index ecb2f51..8dd27fd 100644 --- a/jmdaemon/__init__.py +++ b/jmdaemon/__init__.py @@ -9,6 +9,7 @@ from jmbase.support import get_log from .message_channel import MessageChannel, MessageChannelCollection from .orderbookwatch import OrderbookWatch from jmbase import commands +from .daemon_protocol import JMDaemonServerProtocolFactory # Set default logging handler to avoid "No handler found" warnings. try: diff --git a/jmdaemon/daemon_protocol.py b/jmdaemon/daemon_protocol.py new file mode 100644 index 0000000..adc9053 --- /dev/null +++ b/jmdaemon/daemon_protocol.py @@ -0,0 +1,349 @@ +#! /usr/bin/env python +from __future__ import print_function + +from .message_channel import MessageChannelCollection +from .orderbookwatch import OrderbookWatch +from .enc_wrapper import (as_init_encryption, init_keypair, init_pubkey, + NaclError) +from .protocol import (COMMAND_PREFIX, ORDER_KEYS, NICK_HASH_LENGTH, + NICK_MAX_ENCODED, JM_VERSION, JOINMARKET_NICK_HEADER) +from .irc import IRCMessageChannel + +from jmbase.commands import * +from twisted.protocols import amp +from twisted.internet import reactor +from twisted.internet.protocol import ServerFactory +from twisted.internet.error import (ConnectionLost, ConnectionAborted, + ConnectionClosed, ConnectionDone) +from twisted.python import failure, log +import json +import time +import threading + + +"""Joinmarket application protocol control flow. +For documentation on protocol (formats, message sequence) see +https://github.com/JoinMarket-Org/JoinMarket-Docs/blob/master/ +Joinmarket-messaging-protocol.md +""" +""" +*** +API +*** +The client-daemon two-way communication is documented in jmbase.commands.py +""" + + +class MCThread(threading.Thread): + + def __init__(self, mc): + threading.Thread.__init__(self, name='MCThread') + self.mc = mc + self.daemon = True + + def run(self): + self.mc.run() + + +class JMProtocolError(Exception): + pass + +class JMDaemonServerProtocol(amp.AMP, OrderbookWatch): + + def __init__(self, factory): + self.factory = factory + self.jm_state = 0 + self.restart_mc_required = False + self.irc_configs = None + self.mcc = None + self.sig_lock = threading.Lock() + + def checkClientResponse(self, response): + """A generic check of client acceptance; any failure + is considered criticial. + """ + if 'accepted' not in response or not response['accepted']: + reactor.stop() + + def defaultErrback(self, failure): + failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone, ConnectionLost) + + def defaultCallbacks(self, d): + d.addCallback(self.checkClientResponse) + d.addErrback(self.defaultErrback) + + @JMInit.responder + def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, + maker_timeout_sec): + """Reads in required configuration from client for a new + session; feeds back joinmarket messaging protocol constants + (required for nick creation). + If a new message channel configuration is required, the current + one is shutdown in preparation. + """ + self.maker_timeout_sec = int(maker_timeout_sec) + self.minmakers = int(minmakers) + irc_configs = json.loads(irc_configs) + #(bitcoin) network only referenced in channel name construction + self.network = network + if irc_configs == self.irc_configs: + self.restart_mc_required = False + log.msg("New init received did not require a new message channel" + " setup.") + else: + if self.irc_configs: + #close the existing connections + self.mc_shutdown() + self.irc_configs = irc_configs + self.restart_mc_required = True + mcs = [IRCMessageChannel(c, + daemon=self, + realname='btcint=' + bcsource) + for c in self.irc_configs] + self.mcc = MessageChannelCollection(mcs) + OrderbookWatch.set_msgchan(self, self.mcc) + #register taker-specific msgchan callbacks here + self.mcc.register_taker_callbacks(self.on_error, self.on_pubkey, + self.on_ioauth, self.on_sig) + self.mcc.set_daemon(self) + d = self.callRemote(JMInitProto, + nick_hash_length=NICK_HASH_LENGTH, + nick_max_encoded=NICK_MAX_ENCODED, + joinmarket_nick_header=JOINMARKET_NICK_HEADER, + joinmarket_version=JM_VERSION) + self.defaultCallbacks(d) + return {'accepted': True} + + @JMStartMC.responder + def on_JM_START_MC(self, nick): + """Starts message channel threads, if we are working with + a new message channel configuration. Sets new nick if required. + JM_UP will be called when the welcome messages are received. + """ + self.init_connections(nick) + return {'accepted': True} + + def init_connections(self, nick): + """Sets up message channel connections + if they are not already up; re-sets joinmarket state to 0 + for a new transaction; effectively means any previous + incomplete transaction is wiped. + """ + self.jm_state = 0 #uninited + if self.restart_mc_required: + MCThread(self.mcc).start() + self.restart_mc_required = False + else: + #if we are not restarting the MC, + #we must simulate the on_welcome message: + self.on_welcome() + self.mcc.set_nick(nick) + + + def on_welcome(self): + """Fired when channel indicated state readiness + """ + d = self.callRemote(JMUp) + self.defaultCallbacks(d) + + @JMSetup.responder + def on_JM_SETUP(self, role, n_counterparties): + assert self.jm_state == 0 + assert n_counterparties > 1 + #TODO consider MAKER role implementation here + assert role == "TAKER" + self.requested_counterparties = n_counterparties + self.crypto_boxes = {} + self.kp = init_keypair() + print("Received setup command") + d = self.callRemote(JMSetupDone) + self.defaultCallbacks(d) + #Request orderbook here, on explicit setup request from client, + #assumes messagechannels are in "up" state. Orders are read + #in the callback on_order_seen in OrderbookWatch. + #TODO: pubmsg should not (usually?) fire if already up from previous run. + self.mcc.pubmsg(COMMAND_PREFIX + "orderbook") + self.jm_state = 1 + return {'accepted': True} + + @JMRequestOffers.responder + def on_JM_REQUEST_OFFERS(self): + """Reports the current state of the orderbook. + This call is stateless.""" + rows = self.db.execute('SELECT * FROM orderbook;').fetchall() + self.orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] + log.msg("About to send orderbook of size: " + str(len(self.orderbook))) + string_orderbook = json.dumps(self.orderbook) + d = self.callRemote(JMOffers, + orderbook=string_orderbook) + self.defaultCallbacks(d) + return {'accepted': True} + + @JMFill.responder + def on_JM_FILL(self, amount, commitment, revelation, filled_offers): + if not (self.jm_state == 1 and isinstance(amount, int) and amount >=0): + return {'accepted': False} + self.cjamount = amount + self.commitment = commitment + self.revelation = revelation + #Reset utxo data to null for this new transaction + self.ioauth_data = {} + self.active_orders = json.loads(filled_offers) + for nick, offer_dict in self.active_orders.iteritems(): + offer_fill_msg = " ".join([str(offer_dict["oid"]), str(amount), str( + self.kp.hex_pk()), str(commitment)]) + self.mcc.prepare_privmsg(nick, "fill", offer_fill_msg) + reactor.callLater(self.maker_timeout_sec, self.completeStage1) + self.jm_state = 2 + return {'accepted': True} + + def on_pubkey(self, nick, maker_pk): + """This is handled locally in the daemon; set up e2e + encrypted messaging with this counterparty + """ + if nick not in self.active_orders.keys(): + log.msg("Counterparty not part of this transaction. Ignoring") + return + try: + self.crypto_boxes[nick] = [maker_pk, as_init_encryption( + self.kp, init_pubkey(maker_pk))] + except NaclError as e: + print("Unable to setup crypto box with " + nick + ": " + repr(e)) + self.mcc.send_error(nick, "invalid nacl pubkey: " + maker_pk) + return + self.mcc.prepare_privmsg(nick, "auth", str(self.revelation)) + + def on_ioauth(self, nick, utxo_list, auth_pub, cj_addr, change_addr, + btc_sig): + """Passes through to Taker the information from counterparties once + they've all been received; note that we must also pass back the maker_pk + so it can be verified against the btc-sigs for anti-MITM + """ + if nick not in self.active_orders.keys(): + print("Got an unexpected ioauth from nick: " + str(nick)) + return + self.ioauth_data[nick] = [utxo_list, auth_pub, cj_addr, change_addr, + btc_sig, self.crypto_boxes[nick][0]] + if self.ioauth_data.keys() == self.active_orders.keys(): + #Finish early if we got all + self.respondToIoauths(True) + + def respondToIoauths(self, accepted): + if self.jm_state != 2: + #this can be called a second time on timeout, in which case we + #do nothing + return + d = self.callRemote(JMFillResponse, + success=accepted, + ioauth_data = json.dumps(self.ioauth_data)) + if not accepted: + #Client simply accepts failure TODO + self.defaultCallbacks(d) + else: + #Act differently if *we* provided utxos, but + #client does not accept for some reason + d.addCallback(self.checkUtxosAccepted) + d.addErrback(self.defaultErrback) + + def completeStage1(self): + """Timeout of stage 1 requests; + either send success + ioauth data if enough makers, + else send failure to client. + """ + response = True if len(self.ioauth_data.keys()) >= self.minmakers else False + self.respondToIoauths(response) + + def checkUtxosAccepted(self, accepted): + if not accepted: + log.msg("Taker rejected utxos provided; resetting.") + #TODO create re-set function to start again + else: + #only update state if client accepted + self.jm_state = 3 + + @JMMakeTx.responder + def on_JM_MAKE_TX(self, nick_list, txhex): + if not self.jm_state == 3: + log.msg("Make tx was called in wrong state, rejecting") + return {'accepted': False} + nick_list = json.loads(nick_list) + self.mcc.send_tx(nick_list, txhex) + return {'accepted': True} + + def on_sig(self, nick, sig): + """Pass signature through to Taker. + """ + d = self.callRemote(JMSigReceived, + nick=nick, + sig=sig) + self.defaultCallbacks(d) + + """The following functions handle requests and responses + from client for messaging signing and verifying. + """ + def request_signed_message(self, nick, cmd, msg, msg_to_be_signed, hostid): + """The daemon passes the nick and cmd fields + to the client so it can be echoed back to the privmsg + after return (with signature); note that the cmd is already + inside "msg" after having been parsed in MessageChannel; this + duplication is so that the client does not need to know the + message syntax. + """ + with self.sig_lock: + d = self.callRemote(JMRequestMsgSig, + nick=str(nick), + cmd=str(cmd), + msg=str(msg), + msg_to_be_signed=str(msg_to_be_signed), + hostid=str(hostid)) + self.defaultCallbacks(d) + + def request_signature_verify(self, msg, fullmsg, sig, pubkey, nick, hashlen, + max_encoded, hostid): + with self.sig_lock: + d = self.callRemote(JMRequestMsgSigVerify, + msg=msg, + fullmsg=fullmsg, + sig=sig, + pubkey=pubkey, + nick=nick, + hashlen=hashlen, + max_encoded=max_encoded, + hostid=hostid) + self.defaultCallbacks(d) + + @JMMsgSignature.responder + def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid): + self.mcc.privmsg(nick, cmd, msg_to_return, mc=hostid) + return {'accepted': True} + + @JMMsgSignatureVerify.responder + def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid): + if not verif_result: + log.msg("Verification failed for nick: " + str(nick)) + else: + self.mcc.on_verified_privmsg(nick, fullmsg, hostid) + return {'accepted': True} + + def get_crypto_box_from_nick(self, nick): + if nick in self.crypto_boxes and self.crypto_boxes[nick] != None: + return self.crypto_boxes[nick][1] # libsodium encryption object + else: + log.msg('something wrong, no crypto object, nick=' + nick + + ', message will be dropped') + return None + + def on_error(self): + log.msg("Unimplemented on_error") + + def mc_shutdown(self): + log.msg("Message channels being shutdown by daemon") + if self.mcc: + self.mcc.shutdown() + + +class JMDaemonServerProtocolFactory(ServerFactory): + protocol = JMDaemonServerProtocol + + def buildProtocol(self, addr): + return JMDaemonServerProtocol(self) \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md index 5427b3e..df4bf89 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -37,9 +37,9 @@ This is the same as in normal Joinmarket. ###joinmarketd.py This file's role is explained in the main README in the top level directory. It only -takes one argument, the port it serves on: +takes one argument, the port it serves on (default 27183): - `python joinmarketd.py 12345` + `python joinmarketd.py 27183` ###add-utxo.py diff --git a/scripts/joinmarketd.py b/scripts/joinmarketd.py index 5d4a585..62030d4 100644 --- a/scripts/joinmarketd.py +++ b/scripts/joinmarketd.py @@ -1,351 +1,7 @@ -#! /usr/bin/env python -from __future__ import print_function import sys -from jmdaemon import (IRCMessageChannel, MessageChannelCollection, - OrderbookWatch, as_init_encryption, init_pubkey, - NaclError, init_keypair, COMMAND_PREFIX, ORDER_KEYS, - NICK_HASH_LENGTH, NICK_MAX_ENCODED, JM_VERSION, - JOINMARKET_NICK_HEADER) - -from jmbase.commands import * -from twisted.protocols import amp from twisted.internet import reactor -from twisted.internet.protocol import ServerFactory from twisted.python.log import startLogging, err -from twisted.internet.error import (ConnectionLost, ConnectionAborted, - ConnectionClosed, ConnectionDone) -from twisted.python import failure, log -import json -import time -import threading - - -"""Joinmarket application protocol control flow. -For documentation on protocol (formats, message sequence) see -https://github.com/JoinMarket-Org/JoinMarket-Docs/blob/master/ -Joinmarket-messaging-protocol.md -""" -""" -*** -API -*** -The client-daemon two-way communication is documented in commands.py -""" - - -class MCThread(threading.Thread): - - def __init__(self, mc): - threading.Thread.__init__(self, name='MCThread') - self.mc = mc - self.daemon = True - - def run(self): - self.mc.run() - - -class JMProtocolError(Exception): - pass - -class JMDaemonServerProtocol(amp.AMP, OrderbookWatch): - - def __init__(self, factory): - self.factory = factory - self.jm_state = 0 - self.restart_mc_required = False - self.irc_configs = None - self.mcc = None - self.sig_lock = threading.Lock() - - def checkClientResponse(self, response): - """A generic check of client acceptance; any failure - is considered criticial. - """ - if 'accepted' not in response or not response['accepted']: - reactor.stop() - - def defaultErrback(self, failure): - failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone, ConnectionLost) - - def defaultCallbacks(self, d): - d.addCallback(self.checkClientResponse) - d.addErrback(self.defaultErrback) - - @JMInit.responder - def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, - maker_timeout_sec): - """Reads in required configuration from client for a new - session; feeds back joinmarket messaging protocol constants - (required for nick creation). - If a new message channel configuration is required, the current - one is shutdown in preparation. - """ - self.maker_timeout_sec = int(maker_timeout_sec) - self.minmakers = int(minmakers) - irc_configs = json.loads(irc_configs) - #(bitcoin) network only referenced in channel name construction - self.network = network - if irc_configs == self.irc_configs: - self.restart_mc_required = False - log.msg("New init received did not require a new message channel" - " setup.") - else: - if self.irc_configs: - #close the existing connections - self.mc_shutdown() - self.irc_configs = irc_configs - self.restart_mc_required = True - mcs = [IRCMessageChannel(c, - daemon=self, - realname='btcint=' + bcsource) - for c in self.irc_configs] - self.mcc = MessageChannelCollection(mcs) - OrderbookWatch.set_msgchan(self, self.mcc) - #register taker-specific msgchan callbacks here - self.mcc.register_taker_callbacks(self.on_error, self.on_pubkey, - self.on_ioauth, self.on_sig) - self.mcc.set_daemon(self) - d = self.callRemote(JMInitProto, - nick_hash_length=NICK_HASH_LENGTH, - nick_max_encoded=NICK_MAX_ENCODED, - joinmarket_nick_header=JOINMARKET_NICK_HEADER, - joinmarket_version=JM_VERSION) - self.defaultCallbacks(d) - return {'accepted': True} - - @JMStartMC.responder - def on_JM_START_MC(self, nick): - """Starts message channel threads, if we are working with - a new message channel configuration. Sets new nick if required. - JM_UP will be called when the welcome messages are received. - """ - self.init_connections(nick) - return {'accepted': True} - - def init_connections(self, nick): - """Sets up message channel connections - if they are not already up; re-sets joinmarket state to 0 - for a new transaction; effectively means any previous - incomplete transaction is wiped. - """ - self.jm_state = 0 #uninited - if self.restart_mc_required: - MCThread(self.mcc).start() - self.restart_mc_required = False - else: - #if we are not restarting the MC, - #we must simulate the on_welcome message: - self.on_welcome() - self.mcc.set_nick(nick) - - - def on_welcome(self): - """Fired when channel indicated state readiness - """ - d = self.callRemote(JMUp) - self.defaultCallbacks(d) - - @JMSetup.responder - def on_JM_SETUP(self, role, n_counterparties): - assert self.jm_state == 0 - assert n_counterparties > 1 - #TODO consider MAKER role implementation here - assert role == "TAKER" - self.requested_counterparties = n_counterparties - self.crypto_boxes = {} - self.kp = init_keypair() - print("Received setup command") - d = self.callRemote(JMSetupDone) - self.defaultCallbacks(d) - #Request orderbook here, on explicit setup request from client, - #assumes messagechannels are in "up" state. Orders are read - #in the callback on_order_seen in OrderbookWatch. - #TODO: pubmsg should not (usually?) fire if already up from previous run. - self.mcc.pubmsg(COMMAND_PREFIX + "orderbook") - self.jm_state = 1 - return {'accepted': True} - - @JMRequestOffers.responder - def on_JM_REQUEST_OFFERS(self): - """Reports the current state of the orderbook. - This call is stateless.""" - rows = self.db.execute('SELECT * FROM orderbook;').fetchall() - self.orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] - log.msg("About to send orderbook of size: " + str(len(self.orderbook))) - string_orderbook = json.dumps(self.orderbook) - d = self.callRemote(JMOffers, - orderbook=string_orderbook) - self.defaultCallbacks(d) - return {'accepted': True} - - @JMFill.responder - def on_JM_FILL(self, amount, commitment, revelation, filled_offers): - if not (self.jm_state == 1 and isinstance(amount, int) and amount >=0): - return {'accepted': False} - self.cjamount = amount - self.commitment = commitment - self.revelation = revelation - #Reset utxo data to null for this new transaction - self.ioauth_data = {} - self.active_orders = json.loads(filled_offers) - for nick, offer_dict in self.active_orders.iteritems(): - offer_fill_msg = " ".join([str(offer_dict["oid"]), str(amount), str( - self.kp.hex_pk()), str(commitment)]) - self.mcc.prepare_privmsg(nick, "fill", offer_fill_msg) - reactor.callLater(self.maker_timeout_sec, self.completeStage1) - self.jm_state = 2 - return {'accepted': True} - - def on_pubkey(self, nick, maker_pk): - """This is handled locally in the daemon; set up e2e - encrypted messaging with this counterparty - """ - if nick not in self.active_orders.keys(): - log.msg("Counterparty not part of this transaction. Ignoring") - return - try: - self.crypto_boxes[nick] = [maker_pk, as_init_encryption( - self.kp, init_pubkey(maker_pk))] - except NaclError as e: - print("Unable to setup crypto box with " + nick + ": " + repr(e)) - self.mcc.send_error(nick, "invalid nacl pubkey: " + maker_pk) - return - self.mcc.prepare_privmsg(nick, "auth", str(self.revelation)) - - def on_ioauth(self, nick, utxo_list, auth_pub, cj_addr, change_addr, - btc_sig): - """Passes through to Taker the information from counterparties once - they've all been received; note that we must also pass back the maker_pk - so it can be verified against the btc-sigs for anti-MITM - """ - if nick not in self.active_orders.keys(): - print("Got an unexpected ioauth from nick: " + str(nick)) - return - self.ioauth_data[nick] = [utxo_list, auth_pub, cj_addr, change_addr, - btc_sig, self.crypto_boxes[nick][0]] - if self.ioauth_data.keys() == self.active_orders.keys(): - #Finish early if we got all - self.respondToIoauths(True) - - def respondToIoauths(self, accepted): - if self.jm_state != 2: - #this can be called a second time on timeout, in which case we - #do nothing - return - d = self.callRemote(JMFillResponse, - success=accepted, - ioauth_data = json.dumps(self.ioauth_data)) - if not accepted: - #Client simply accepts failure TODO - self.defaultCallbacks(d) - else: - #Act differently if *we* provided utxos, but - #client does not accept for some reason - d.addCallback(self.checkUtxosAccepted) - d.addErrback(self.defaultErrback) - - def completeStage1(self): - """Timeout of stage 1 requests; - either send success + ioauth data if enough makers, - else send failure to client. - """ - response = True if len(self.ioauth_data.keys()) >= self.minmakers else False - self.respondToIoauths(response) - - def checkUtxosAccepted(self, accepted): - if not accepted: - log.msg("Taker rejected utxos provided; resetting.") - #TODO create re-set function to start again - else: - #only update state if client accepted - self.jm_state = 3 - - @JMMakeTx.responder - def on_JM_MAKE_TX(self, nick_list, txhex): - if not self.jm_state == 3: - log.msg("Make tx was called in wrong state, rejecting") - return {'accepted': False} - nick_list = json.loads(nick_list) - self.mcc.send_tx(nick_list, txhex) - return {'accepted': True} - - def on_sig(self, nick, sig): - """Pass signature through to Taker. - """ - d = self.callRemote(JMSigReceived, - nick=nick, - sig=sig) - self.defaultCallbacks(d) - - """The following functions handle requests and responses - from client for messaging signing and verifying. - """ - def request_signed_message(self, nick, cmd, msg, msg_to_be_signed, hostid): - """The daemon passes the nick and cmd fields - to the client so it can be echoed back to the privmsg - after return (with signature); note that the cmd is already - inside "msg" after having been parsed in MessageChannel; this - duplication is so that the client does not need to know the - message syntax. - """ - with self.sig_lock: - d = self.callRemote(JMRequestMsgSig, - nick=str(nick), - cmd=str(cmd), - msg=str(msg), - msg_to_be_signed=str(msg_to_be_signed), - hostid=str(hostid)) - self.defaultCallbacks(d) - - def request_signature_verify(self, msg, fullmsg, sig, pubkey, nick, hashlen, - max_encoded, hostid): - with self.sig_lock: - d = self.callRemote(JMRequestMsgSigVerify, - msg=msg, - fullmsg=fullmsg, - sig=sig, - pubkey=pubkey, - nick=nick, - hashlen=hashlen, - max_encoded=max_encoded, - hostid=hostid) - self.defaultCallbacks(d) - - @JMMsgSignature.responder - def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid): - self.mcc.privmsg(nick, cmd, msg_to_return, mc=hostid) - return {'accepted': True} - - @JMMsgSignatureVerify.responder - def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid): - if not verif_result: - log.msg("Verification failed for nick: " + str(nick)) - else: - self.mcc.on_verified_privmsg(nick, fullmsg, hostid) - return {'accepted': True} - - def get_crypto_box_from_nick(self, nick): - if nick in self.crypto_boxes and self.crypto_boxes[nick] != None: - return self.crypto_boxes[nick][1] # libsodium encryption object - else: - log.msg('something wrong, no crypto object, nick=' + nick + - ', message will be dropped') - return None - - def on_error(self): - log.msg("Unimplemented on_error") - - def mc_shutdown(self): - log.msg("Message channels being shutdown by daemon") - if self.mcc: - self.mcc.shutdown() - - -class JMDaemonServerProtocolFactory(ServerFactory): - protocol = JMDaemonServerProtocol - - def buildProtocol(self, addr): - return JMDaemonServerProtocol(self) +import jmdaemon def startup_joinmarketd(port, finalizer=None, finalizer_args=None): """Start event loop for joinmarket daemon here. @@ -354,8 +10,8 @@ def startup_joinmarketd(port, finalizer=None, finalizer_args=None): finalizer: a function which is called after the reactor has shut down. finalizer_args : arguments to finalizer function. """ - log.startLogging(sys.stdout) - factory = JMDaemonServerProtocolFactory() + startLogging(sys.stdout) + factory = jmdaemon.JMDaemonServerProtocolFactory() reactor.listenTCP(port, factory) if finalizer: reactor.addSystemEventTrigger("after", "shutdown", finalizer, @@ -364,5 +20,8 @@ def startup_joinmarketd(port, finalizer=None, finalizer_args=None): if __name__ == "__main__": - port = int(sys.argv[1]) + if len(sys.argv) < 2: + port = 27183 + else: + port = int(sys.argv[1]) startup_joinmarketd(port) diff --git a/scripts/sendpayment.py b/scripts/sendpayment.py index c377f69..a7945d8 100644 --- a/scripts/sendpayment.py +++ b/scripts/sendpayment.py @@ -15,7 +15,7 @@ Moreover, it can run several transactions as specified in a "schedule", like: call it like the normal Joinmarket sendpayment, but optionally add a port for the daemon: -`python sendpayment.py -p 12345 -N 3 -m 1 walletseed amount address`; +`python sendpayment.py -p 27183 -N 3 -m 1 walletseed amount address`; Schedule can be read from a file with the -S option, in which case no need to provide amount, mixdepth, number of counterparties or destination from command line. @@ -104,7 +104,7 @@ def main(): type='int', dest='daemonport', help='port on which joinmarketd is running', - default='12345') + default='27183') parser.add_option('-S', '--schedule-file', type='str',