@ -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
@ -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
@ -174,7 +174,6 @@ class HTTPPassThrough(amp.AMP):
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 != " " ]
@ -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 " ] )
@ -325,7 +323,7 @@ class BIP78ServerProtocol(HTTPPassThrough):
"""
"""
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
@ -355,7 +353,7 @@ class BIP78ServerProtocol(HTTPPassThrough):
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 }
@ -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
@ -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
@ -659,9 +655,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
"""
"""
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 :
@ -682,14 +676,13 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
"""
"""
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 " ] = utxoli st
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
@ -709,7 +702,6 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
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 }
@ -798,15 +790,15 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
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 )
@ -831,20 +823,14 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
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 .
"""
"""
try :
d = self . callRemote ( JMTXBroadcast , tx = tx )
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 .
"""
"""
@ -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 ) :
@ -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 ) :