diff --git a/jmclient/jmclient/taker.py b/jmclient/jmclient/taker.py index fb728d3..714287b 100644 --- a/jmclient/jmclient/taker.py +++ b/jmclient/jmclient/taker.py @@ -28,7 +28,6 @@ class Taker(object): def __init__(self, wallet, schedule, - answeryes, order_chooser=weighted_order_choose, sign_method=None, callbacks=None): @@ -43,7 +42,6 @@ class Taker(object): """ self.wallet = wallet self.schedule = schedule - self.answeryes = answeryes self.order_chooser = order_chooser self.ignored_makers = None self.txid = None @@ -51,48 +49,23 @@ class Taker(object): #allow custom wallet-based clients to use their own signing code; #currently only setting "wallet" is allowed, calls wallet.sign_tx(tx) self.sign_method = sign_method - #External callers can set any of the 3 callbacks for filtering orders, + #External callers set the 3 callbacks for filtering orders, #sending info messages to client, and action on completion. + #"None" is allowable for taker_info_callback, defaults to log msg. if callbacks: + """Signature of filter_orders: + args: orders_fees, cjamount + returns: boolean representing accept/reject + """ self.filter_orders_callback = callbacks[0] - if not self.filter_orders_callback: - self.filter_orders_callback = self.default_filter_orders_callback self.taker_info_callback = callbacks[1] if not self.taker_info_callback: self.taker_info_callback = self.default_taker_info_callback self.on_finished_callback = callbacks[2] - if not self.on_finished_callback: - self.on_finished_callback = self.default_on_finished_callback def default_taker_info_callback(self, infotype, msg): jlog.debug(infotype + ":" + msg) - def default_filter_orders_callback(self, orders_fees): - orders, total_cj_fee = orders_fees - jlog.info("Chose these orders: " +pprint.pformat(orders)) - jlog.info('total cj fee = ' + str(total_cj_fee)) - total_fee_pc = 1.0 * total_cj_fee / self.cjamount - jlog.info('total coinjoin fee = ' + str(float('%.3g' % ( - 100.0 * total_fee_pc))) + '%') - WARNING_THRESHOLD = 0.02 # 2% - if total_fee_pc > WARNING_THRESHOLD: - jlog.info('\n'.join(['=' * 60] * 3)) - jlog.info('WARNING ' * 6) - jlog.info('\n'.join(['=' * 60] * 1)) - jlog.info('OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.') - jlog.info('\n'.join(['=' * 60] * 1)) - jlog.info('WARNING ' * 6) - jlog.info('\n'.join(['=' * 60] * 3)) - if not self.answeryes: - if raw_input('send with these orders? (y/n):')[0] != 'y': - self.on_finished_callback(False) - return False - return True - - def default_on_finished_callback(self, result, fromtx=False): - """Currently not possible without access to the client protocol factory""" - raise NotImplementedError - def initialize(self, orderbook): """Once the daemon is active and has returned the current orderbook, select offers, re-initialize variables and prepare a commitment, @@ -150,8 +123,10 @@ class Taker(object): self.ignored_makers) if self.filter_orders_callback: accepted = self.filter_orders_callback([self.orderbook, - self.total_cj_fee]) + self.total_cj_fee], + self.cjamount) if not accepted: + self.on_finished_callback(False) return False return True @@ -211,7 +186,8 @@ class Taker(object): "Could not find orders to complete transaction") self.on_finished_callback(False) return False - if not self.filter_orders_callback((self.orderbook, total_cj_fee)): + if not self.filter_orders_callback((self.orderbook, self.total_cj_fee), + self.cjamount): self.on_finished_callback(False) return False @@ -262,7 +238,6 @@ class Taker(object): #this will not be added to the transaction, so we will have #to recheck if we have enough continue - total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.orderbook[nick]['ordertype'], self.orderbook[nick]['cjfee'], @@ -367,8 +342,13 @@ class Taker(object): address/pubkey that will be used for coinjoining with an ecdsa verification. """ - if not btc.ecdsa_verify(maker_pk, btc_sig, auth_pub): - jlog.debug('signature didnt match pubkey and message') + try: + if not btc.ecdsa_verify(maker_pk, btc_sig, auth_pub): + jlog.debug('signature didnt match pubkey and message') + return False + except Exception as e: + jlog.info("Failed ecdsa verify for maker pubkey: " + str(maker_pk)) + jlog.info("Exception was: " + repr(e)) return False return True @@ -383,8 +363,8 @@ class Taker(object): for index, ins in enumerate(self.latest_tx['ins']): utxo_for_checking = ins['outpoint']['hash'] + ':' + str(ins[ 'outpoint']['index']) - if (ins['script'] != '' or - utxo_for_checking in self.input_utxos.keys()): + #'deadbeef' markers mean our own input scripts are not '' + if (ins['script'] != ''): continue utxo[ctr] = [index, utxo_for_checking] ctr += 1 @@ -534,17 +514,15 @@ class Taker(object): if self.my_cj_addr: return self.my_cj_addr else: - addr, self.sign_k = donation_address() - return addr + #Note: donation code removed (possibly temporarily) + raise NotImplementedError def sign_tx(self, tx, i, priv): if self.my_cj_addr: return btc.sign(tx, i, priv) else: - return btc.sign(tx, - i, - priv, - usenonce=btc.safe_hexlify(self.sign_k)) + #Note: donation code removed (possibly temporarily) + raise NotImplementedError def self_sign(self): # now sign it ourselves diff --git a/jmclient/test/taker_test_data.py b/jmclient/test/taker_test_data.py new file mode 100644 index 0000000..0b3dc67 --- /dev/null +++ b/jmclient/test/taker_test_data.py @@ -0,0 +1,190 @@ +#orderbook +t_orderbook = [{u'counterparty': u'J5FA1Gj7Ln4vSGne', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, + {u'counterparty': u'J5CFffuuewjG44UJ', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, + {u'counterparty': u'J55z23xdjxJjC7er', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, + {u'counterparty': u'J54Ghp5PXCdY9H3t', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, + {u'counterparty': u'J559UPUSLLjHJpaB', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}, + {u'counterparty': u'J5cBx1FwUVh9zzoO', u'ordertype': u'reloffer', u'oid': 0, + u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'}] + +t_dest_addr = "mvw1NazKDRbeNufFANqpYNAANafsMC2zVU" + +t_chosen_orders = {u'J559UPUSLLjHJpaB': {u'cjfee': u'0.0002', + u'counterparty': u'J559UPUSLLjHJpaB', + u'maxsize': 599972700, + u'minsize': 7500000, + u'oid': 0, + u'ordertype': u'reloffer', + u'txfee': 1000}, + u'J55z23xdjxJjC7er': {u'cjfee': u'0.0002', + u'counterparty': u'J55z23xdjxJjC7er', + u'maxsize': 599972700, + u'minsize': 7500000, + u'oid': 0, + u'ordertype': u'reloffer', + u'txfee': 1000}, + u'J5CFffuuewjG44UJ': {u'cjfee': u'0.0002', + u'counterparty': u'J5CFffuuewjG44UJ', + u'maxsize': 599972700, + u'minsize': 7500000, + u'oid': 0, + u'ordertype': u'reloffer', + u'txfee': 1000}} + +""" +2016-12-01 15:27:33,351 [MainThread ] [INFO ] total cj fee = 63000 +2016-12-01 15:27:33,351 [MainThread ] [INFO ] total coinjoin fee = 0.0573% +2016-12-01 15:27:34,887 [MainThread ] [DEBUG] INFO:Preparing bitcoin data.. +2016-12-01 15:27:34,888 [MainThread ] [DEBUG] rpc: getaccount ['myzi6K9vt88rdiXpYayfJkU1x33G1wz2fP'] +2016-12-01 15:27:34,889 [MainThread ] [DEBUG] total estimated amount spent = 110093000 +""" +t_utxos_by_mixdepth = {0: {u'534b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': {'address': u'mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ', + 'value': 200000000}}, + 1: {u'0780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1': {'address': u'mvtY8DVgn3TtvjHbVsauYoSQjAhNqVyqmM', + 'value': 200000000}, + u'7e574db96a4d43a99786b3ea653cda9e4388f377848f489332577e018380cff1:0': {'address': u'n3nELhmU2D7ebGYzJnGFWgVDK3cYErmTcQ', + 'value': 200000000}, + u'dd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': {'address': u'mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf', + 'value': 200000000}}, + 2: {}, + 3: {}, + 4: {}} + +t_selected_utxos = [{'utxo': u'0780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1', + 'value': 200000000}] + +t_generated_podle = {'P': '025a2e04dc6bd5f58fe4eb13045b27f0dd17c39524264639f48607347cf6d69c4e', + 'P2': '0223e54e9917d8482f1b54ead8e941907c17051b95397e8bc110adc6681d8d44c8', + 'commit': 'aa0545c9ed918e66f86df467c96a4978529b836aa4688df682a2db4e27d4ed9d', + 'e': '5b7ab1fa21287bbf0df4a0c46f6c31c3f17887ee9ea6ae584fc3a861ae9f1e9d', + 'sig': 'ebe25d7b2d667de802677c30c6fea07386f0cd67d4e4c795e4a6ebc39b21eb39', + 'used': 'False', + 'utxo': u'0780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1'} + +t_maker_response = {"J559UPUSLLjHJpaB": + [["03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1"], + "03a2d1cbe977b1feaf8d0d5cc28c686859563d1520b28018be0c2661cf1ebe4857", + "mrKTGvFfYUEqk52qPKUroumZJcpjHLQ6pn", + "mxPnzFkCQpPzVQdajNLoT4us5pTPsQZZZp", + "MEQCIBeGrtxxVrj5tSUX6vEetmzE8nRBG/guSXq3SrqypIt5AiAnIZzDUXu8DtODgF2p1Bo27L8VcG1GJSfatZbS23YZQQ==", + "5bcc7ae1a3530e454812668620aced47d774bf06a1f5870d531422a1a958b629"], + "J55z23xdjxJjC7er": + [["498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0"], + "02b4b749d54e96b04066b0803e372a43d6ffa16e75a001ae0ed4b235674ab286be", + "mhatyHdna3Qt5FtnfwWaMVV1dohCaDYF3T", + "mjJoVN2HCUGVDvNebiFnHdB3zF56bxQm5z", + "MEQCIBlMF7DRbhr14e74He9m+UYjR5y8jjvP7TvUh8valebmAiBoIGjl436fsYim9pKSTbCKiBmT82hQ98LvIOGSLprk0A==", + "8204d1cba30d4cdabab16a5e8d10d17464e24c78a6f887ae2d920b223c030d28"], + "J5CFffuuewjG44UJ": + [["3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1"], + "023bcbafb4f68455e0d1d117c178b0e82a84e66414f0987453d78da034b299c3a9", + "mpAEocXy8ckcJBo3fhQg9Mv1kfEzAuUivX", + "n29NWbsyq5MjCMC5ykjStd78zwfjvCvJJZ", + "MEUCIQDAM5Aa0aU5iKI0b9YnNtwH0m+6sz3zeTL8f398CPjuQAIgLeU9mCJX8SupNNMkA+bsUJeRYe3kiLnzq3OlmXTxck0=", + "7377d03477485884e0129dbdb2d79f4956f5b74366d805385b6f127509a8433f"]} + +""" +2016-12-01 15:27:39,914 [MainThread ] [DEBUG] rpc: gettxout [u'03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6', 1, False] +2016-12-01 15:27:39,915 [MainThread ] [DEBUG] fee breakdown for J559UPUSLLjHJpaB totalin=200000000 cjamount=110000000 txfee=1000 realcjfee=22000 +2016-12-01 15:27:39,915 [MainThread ] [DEBUG] rpc: gettxout [u'498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3', 0, False] +2016-12-01 15:27:39,915 [MainThread ] [DEBUG] fee breakdown for J55z23xdjxJjC7er totalin=200000000 cjamount=110000000 txfee=1000 realcjfee=22000 +2016-12-01 15:27:39,916 [MainThread ] [DEBUG] rpc: gettxout [u'3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c', 1, False] +2016-12-01 15:27:39,916 [MainThread ] [DEBUG] fee breakdown for J5CFffuuewjG44UJ totalin=200000000 cjamount=110000000 txfee=1000 realcjfee=22000 +2016-12-01 15:27:39,916 [MainThread ] [DEBUG] INFO:Got all parts, enough to build a tx +2016-12-01 15:27:39,917 [MainThread ] [DEBUG] Estimated transaction size: 870 +2016-12-01 15:27:39,917 [MainThread ] [DEBUG] rpc: estimatefee [3] +2016-12-01 15:27:39,917 [MainThread ] [DEBUG] got estimated tx bytes: 870 +2016-12-01 15:27:39,917 [MainThread ] [INFO ] Based on initial guess: 30000, we estimated a miner fee of: 26100 +2016-12-01 15:27:39,918 [MainThread ] [INFO ] fee breakdown for me totalin=200000000 my_txfee=23100 makers_txfee=3000 cjfee_total=66000 => changevalue=89910900 +""" + +t_obtained_tx = {'ins': [{'outpoint': {'hash': '03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6', + 'index': 1}, + 'script': '', + 'sequence': 4294967295}, + {'outpoint': {'hash': '3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c', + 'index': 1}, + 'script': '', + 'sequence': 4294967295}, + {'outpoint': {'hash': '498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3', + 'index': 0}, + 'script': '', + 'sequence': 4294967295}, + {'outpoint': {'hash': '0780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75', + 'index': 1}, + 'script': '', + 'sequence': 4294967295}], + 'locktime': 0, + 'outs': [{'script': '76a914767c956efe6092a775fea39a06d1cac9aae956d788ac', + 'value': 110000000}, + {'script': '76a914cab20e3270988ac99651b8f079a3b4c93b996a6888ac', + 'value': 89910900}, + {'script': '76a914b91f75254b5fa1510cc944a2206ed72235d0d88188ac', + 'value': 90021000}, + {'script': '76a914a916707952c2df28a3abf3ee692dfbbd5a4d74dc88ac', + 'value': 110000000}, + {'script': '76a914e245b480b46bcbc9d13e68766ad19909decd135288ac', + 'value': 90021000}, + {'script': '76a91416af241bb1db02dfd7c65989bbab190ac489ccc188ac', + 'value': 110000000}, + {'script': '76a9142994295a9d4d083eb792e669e3211007dc78928888ac', + 'value': 90021000}, + {'script': '76a9145ece2dac945c8ff5b2b6635360ca0478ade305d488ac', + 'value': 110000000}], + 'version': 1} + +#signatures from makers +""" +nick=J5CFffuuewjG44UJ message=!sig xH9IAMo2fvG+g+DAbLNOPsGsJCDm6r+ZY5QM7p+SRsixbqSwXcBQAn7Mnw1rS+uGlrJkM8ossX5VHKjdKDhTXQVLawR7XgiVFFFiO+/FjdFhqVuS4Q/NgOlb7nCBe/UaBebd9NpuURG+8u/V+46jtqKRtVsSO1+QZQBt2nSpYCqxWIjxMowRxS4O/zlrOVbyjv/AjchOajufKJwckkrkJDyQDYlUdW+eqs43tf0XsJ9k4NHRVVHAQQ== 036558f550b1d398d2325d892e50ef25b0f663ae13f70d0b304a15f07030061ace MEUCIQCE9MgU+HfcHkKE8zNzNeCEdDBJuQatA6C2sTJ9mVKK7wIgX4w9r0tz4s9qeuW0UjNliDatJ4X7pS3/atADSqPat0U= +nick=J559UPUSLLjHJpaB message=!sig geHTf1n88eKeUnVOj7bIrJF1KFCN03IQhZD0cR17Q7jPSn2DZrrvMaRNkjZRyF+zGnWFwd69kwLRU0ftCaMf/3lw+05UovVCREiyXWUtPJa7XAY2NW4iMmTnGTp8f9RLgDcDhiZayKXTpzBDC9r6WAt6wiD0lej5uw7dmluKSUyfXW8sOYPmLm4iJAPcbGeJiQfiR9zBeX8w+6Kz4bkaiue41SzQP/h9avPV2XIX4kVQQ3jLfQyHww== 038f90ab260df440cef82a981146b509eb9df019884e145158230e8babc17d7be4 MEQCIEo5Pau9zqW2lw+B2AYTYuTO5TDbBkgsOk0bqT+SQctKAiBO1nbsmYTy7E0Qd7jAxko1Gq6Yk0Q6DerByuEuk5IBSQ== +nick=J55z23xdjxJjC7er message=!sig A5CWvqmYCOiZBEEi9iHVpQL0oO9B7VIIzuU9QhkzXOw+iD916C9b+Yk3eTxrtf+qaLARQ7eui6zdPNek95EdmqCEqM/myeeuBVSy9KrcB9xU0sdnuCu4+g13jVe9Pkvd1iizZ8GCNP7SejEzeltNr0a1lR+M0kKtj4XI+nDTxhisSzL8PDXsqoOMcrDjegna3TZsJeKviu8r/1T/zWwTQtRCXqruLnflqXNLtZoyFmoaO1GurgkNHA== 029a8beadec242f04f2295787ac0175b960e2d68d115ec65c4310de7ce3fa2cec0 MEQCIHpTxVkwtvm7agbp47Z5V0We8jxXkfZDUFsW2tZwTZdHAiA9JnYvo74hF3RihzHw2l+ufTOmC/3ddBpxkB9+AdZvzA== +""" + +""" +2016-12-01 15:27:39,921 [MainThread ] [DEBUG] INFO:Built tx, sending to counterparties. +2016-12-01 15:27:39,968 [MainThread ] [DEBUG] rpc: gettxout ['03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6', 1, False] +2016-12-01 15:27:39,968 [MainThread ] [DEBUG] rpc: gettxout ['3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c', 1, False] +2016-12-01 15:27:39,969 [MainThread ] [DEBUG] rpc: gettxout ['498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3', 0, False] +2016-12-01 15:27:39,971 [MainThread ] [DEBUG] found good sig at index=1 +2016-12-01 15:27:39,971 [MainThread ] [DEBUG] nick = J5CFffuuewjG44UJ sent all sigs, removing from nonrespondant list +2016-12-01 15:27:39,971 [MainThread ] [DEBUG] rpc: gettxout ['03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6', 1, False] +2016-12-01 15:27:39,972 [MainThread ] [DEBUG] rpc: gettxout ['498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3', 0, False] +2016-12-01 15:27:39,973 [MainThread ] [DEBUG] found good sig at index=0 +2016-12-01 15:27:39,973 [MainThread ] [DEBUG] nick = J559UPUSLLjHJpaB sent all sigs, removing from nonrespondant list +2016-12-01 15:27:43,937 [MainThread ] [DEBUG] rpc: gettxout ['498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3', 0, False] +2016-12-01 15:27:43,938 [MainThread ] [DEBUG] found good sig at index=2 +2016-12-01 15:27:43,938 [MainThread ] [DEBUG] nick = J55z23xdjxJjC7er sent all sigs, removing from nonrespondant list +2016-12-01 15:27:43,938 [MainThread ] [DEBUG] all makers have sent their signatures +2016-12-01 15:27:43,938 [MainThread ] [DEBUG] INFO:Transaction is valid, signing.. +2016-12-01 15:27:43,943 [MainThread ] [DEBUG] +""" + +t_raw_signed_tx = "0100000004f65091c0d60f75020234f07dee9246db32af6a8f30f833138a279e654a3f2403010000006b483045022100ad522388ce9eacf2760e4d6bd6a114a0e15b88879b430fbb2e60df947494df2402201f49338726599eb0980873aef268d8d890de2792967ff28f0c11eb35e54ff07a012103a2d1cbe977b1feaf8d0d5cc28c686859563d1520b28018be0c2661cf1ebe4857ffffffff3c2abd571046e93a0471c6e47a061bcbfa982c392c1ddcd88ae006d720a83e3f010000006a473044022012bbfa6ef7b0416e00001d90b022d6663f5fd57d9a07bb70b887510f7c44902d022059b382bfb1ff5588a518fc69c55b2ff67d3facc11088e984d7c09c336d4875330121023bcbafb4f68455e0d1d117c178b0e82a84e66414f0987453d78da034b299c3a9ffffffffc3d450f208681405a0c7f0b46816f2121ef302e20c6b3c443b4f53228baa8f49000000006b48304502210081019ea7b68130da4230fd748668c776043004843de50e07bb5fcb42e7632aed022000a34878274e583eec64815d1b587e7fbcd9ac714e722773ac1e69f1209f2e10012102b4b749d54e96b04066b0803e372a43d6ffa16e75a001ae0ed4b235674ab286beffffffff755d83f964d24f33de8f193a10932400bafcb47b9919351af0bf81e3e5d68007010000006b483045022100add68e9532a50ca5585999290531f26e515bdf3d001519b0de8dd6b981daec7f02200b34c58ce61e6673c9efc5bf82cacd4d02673bc1c6cbaba45b5c65579776b8180121025a2e04dc6bd5f58fe4eb13045b27f0dd17c39524264639f48607347cf6d69c4effffffff0880778e06000000001976a914767c956efe6092a775fea39a06d1cac9aae956d788ac74ee5b05000000001976a914cab20e3270988ac99651b8f079a3b4c93b996a6888ac889c5d05000000001976a914b91f75254b5fa1510cc944a2206ed72235d0d88188ac80778e06000000001976a914a916707952c2df28a3abf3ee692dfbbd5a4d74dc88ac889c5d05000000001976a914e245b480b46bcbc9d13e68766ad19909decd135288ac80778e06000000001976a91416af241bb1db02dfd7c65989bbab190ac489ccc188ac889c5d05000000001976a9142994295a9d4d083eb792e669e3211007dc78928888ac80778e06000000001976a9145ece2dac945c8ff5b2b6635360ca0478ade305d488ac00000000" +t_txid = "4d5bfad9bbfb93eb1e25fb2e6c832323d1bf39e63f6ed2319b65e85354c7ca70" + +t_dummy_ext = {"used": [], "external": { + "79f1b8df7d0978f30028487c6c4e0eae96d1aa18e01f13bb4cba6788590cd431:1": { + "reveal": { + "1": { + "P2": "0329d4b4bb28c1a0747c1a5daad59763a9021b5e1fa957887a90c7849789a683b6", + "s": "a303cad939fb773dd16a81c44f210afe0b985a2cf9a63b033139455b70c77be6", + "e": "64f5b9861b95434ab84bd044b93a28f85ea94b474237992d899bd4302eef3820" + }, + "0": { + "P2": "02681ed66595daf98b12d6d69d8afb8d14a531eeaea1161bce8b9f2666ea55f157", + "s": "ed994ad173431bd0f53c82fee70d202e9c2adce492b6226d3cb4116cc3a08383", + "e": "1dd7f56fe83ca66e89b3ec3b73fa44edacab0ef4524652c415065dbf91500c85" + }, + "2": { + "P2": "02cdd5ced7e79bdb651d6d1883e0047509793a9a9e3da4ae516b8a853b9cdd8e98", + "s": "39a19287c4bacc823559d0e1b907e311c31d8a13f45fe30d10b133561113515c", + "e": "a0e7cd319c7e51c6f9e503e95d08c3d2398f9b546c2d64178b6c113c63c29d78" + } + }, + "P": "033749d513d0e0239a75892556a6ce01c3e48f82e75169129abe8ef370ab992c94" + }}} diff --git a/jmclient/test/test_taker.py b/jmclient/test/test_taker.py new file mode 100644 index 0000000..af51a13 --- /dev/null +++ b/jmclient/test/test_taker.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python +from __future__ import print_function +from jmclient import AbstractWallet +from jmclient import BlockchainInterface +from jmclient import Taker +import jmbitcoin as bitcoin +import binascii +import os +import copy +import shutil +import pytest +import json +from base64 import b64encode +from jmclient import (load_program_config, jm_single, set_commitment_file, + get_commitment_file) +from taker_test_data import (t_utxos_by_mixdepth, t_selected_utxos, t_orderbook, + t_maker_response, t_chosen_orders, t_dummy_ext) + +class DummyWallet(AbstractWallet): + + def __init__(self): + super(DummyWallet, self).__init__() + self.max_mix_depth = 5 + self.inject_addr_get_failure = False + + def get_utxos_by_mixdepth(self): + return t_utxos_by_mixdepth + + def select_utxos(self, mixdepth, amount): + if amount > self.get_balance_by_mixdepth()[mixdepth]: + raise Exception("Not enough funds") + return self.get_utxos_by_mixdepth()[mixdepth] + + def get_internal_addr(self, mixing_depth): + if self.inject_addr_get_failure: + raise Exception("address get failure") + return "mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf" + + def sign_tx(self, tx, addrs): + print("Pretending to sign on addresses: " + str(addrs)) + return tx + + def get_key_from_addr(self, addr): + """usable addresses: privkey all 1s, 2s, 3s, ... :""" + privs = [x*32 + "\x01" for x in [chr(y) for y in range(1,6)]] + addrs = {} + """ + mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ + n31WD8pkfAjg2APV78GnbDTdZb1QonBi5D + mmVEKH61BZbLbnVEmk9VmojreB4G4PmBPd + msxyyydNXTiBmt3SushXbH5Qh2ukBAThk3 + musGZczug3BAbqobmYherywCwL9REgNaNm + """ + for p in privs: + addrs[p] = bitcoin.privkey_to_address(p, False, magicbyte=0x6f) + for p, a in addrs.iteritems(): + if a == addr: + return binascii.hexlify(p) + raise ValueError("No such keypair") + +class DummyBlockchainInterface(BlockchainInterface): + def __init__(self): + self.fake_query_results = None + self.qusfail = False + + def sync_addresses(self, wallet): + pass + def sync_unspent(self, wallet): + pass + def add_tx_notify(self, + txd, + unconfirmfun, + confirmfun, + notifyaddr, + timeoutfun=None): + pass + + def pushtx(self, txhex): + print("pushing: " + str(txhex)) + return True + + def insert_fake_query_results(self, fqr): + self.fake_query_results = fqr + + def setQUSFail(self, state): + self.qusfail = state + + def query_utxo_set(self, txouts,includeconf=False): + if self.qusfail: + #simulate failure to find the utxo + return [None] + if self.fake_query_results: + result = [] + for x in self.fake_query_results: + for y in txouts: + if y == x['utxo']: + result.append(x) + return result + result = [] + #external maker utxos + known_outs = {"03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1": "03a2d1cbe977b1feaf8d0d5cc28c686859563d1520b28018be0c2661cf1ebe4857", + "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0": "02b4b749d54e96b04066b0803e372a43d6ffa16e75a001ae0ed4b235674ab286be", + "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1": "023bcbafb4f68455e0d1d117c178b0e82a84e66414f0987453d78da034b299c3a9"} + #our wallet utxos, faked, for podle tests: utxos are doctored (leading 'f'), + #and the lists are (amt, age) + wallet_outs = {'f34b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': [10000000, 2], + 'f780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1': [20000000, 6], + 'fe574db96a4d43a99786b3ea653cda9e4388f377848f489332577e018380cff1:0': [50000000, 3], + 'fd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': [50000000, 6]} + + if includeconf and set(txouts).issubset(set(wallet_outs)): + #includeconf used as a trigger for a podle check; + #here we simulate a variety of amount/age returns + results = [] + for to in txouts: + results.append({'value': wallet_outs[to][0], + 'confirms': wallet_outs[to][1]}) + return results + if txouts[0] in known_outs: + return [{'value': 200000000, + 'address': bitcoin.pubkey_to_address(known_outs[txouts[0]], magicbyte=0x6f), + 'confirms': 20}] + for t in txouts: + result_dict = {'value': 10000000000, + 'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ"} + if includeconf: + result_dict['confirms'] = 20 + result.append(result_dict) + return result + + def estimate_fee_per_kb(self, N): + return 30000 + +def dummy_order_chooser(): + return t_chosen_orders + +def taker_finished(res, fromtx=False): + print("called taker finished callback") + +def dummy_filter_orderbook(orders_fees, cjamount): + print("calling dummy filter orderbook") + return True + +def get_taker(schedule=None, schedule_len=0, sign_method=None, on_finished=None, + filter_orders=None): + if not schedule: + schedule = ['a']*schedule_len #note, for taker.initalize() this will result in junk + print("Using schedule: " + str(schedule)) + on_finished_callback = on_finished if on_finished else taker_finished + filter_orders_callback = filter_orders if filter_orders else dummy_filter_orderbook + return Taker(DummyWallet(), schedule, + callbacks=[filter_orders_callback, None, on_finished_callback], + sign_method=sign_method) + +def test_filter_rejection(createcmtdata): + def filter_orders_reject(orders_feesl, cjamount): + print("calling filter orders rejection") + return False + taker = get_taker(filter_orders=filter_orders_reject) + taker.schedule = [(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")] + res = taker.initialize(t_orderbook) + assert not res[0] + taker = get_taker(filter_orders=filter_orders_reject) + taker.schedule = [(0, 0, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")] + res = taker.initialize(t_orderbook) + assert not res[0] + +@pytest.mark.parametrize( + "failquery, external", + [ + (False, False), + (True, False), + (False, True), + ]) +def test_make_commitment(createcmtdata, failquery, external): + def clean_up(): + jm_single().config.set("POLICY", "taker_utxo_age", old_taker_utxo_age) + jm_single().config.set("POLICY", "taker_utxo_amtpercent", old_taker_utxo_amtpercent) + set_commitment_file(old_commitment_file) + jm_single().bc_interface.setQUSFail(False) + os.remove('dummyext') + old_commitment_file = get_commitment_file() + with open('dummyext', 'wb') as f: + f.write(json.dumps(t_dummy_ext, indent=4)) + if external: + set_commitment_file('dummyext') + old_taker_utxo_age = jm_single().config.get("POLICY", "taker_utxo_age") + old_taker_utxo_amtpercent = jm_single().config.get("POLICY", "taker_utxo_amtpercent") + jm_single().config.set("POLICY", "taker_utxo_age", "5") + jm_single().config.set("POLICY", "taker_utxo_amtpercent", "20") + mixdepth = 0 + amount = 110000000 + taker = get_taker([(mixdepth, amount, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]) + taker.wallet.unspent = {'f34b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': + {'address': u'n31WD8pkfAjg2APV78GnbDTdZb1QonBi5D', + 'value': 10000000}, + 'f780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1': + {'address': u'mmVEKH61BZbLbnVEmk9VmojreB4G4PmBPd', + 'value': 20000000}, + 'fe574db96a4d43a99786b3ea653cda9e4388f377848f489332577e018380cff1:0': + {'address': u'msxyyydNXTiBmt3SushXbH5Qh2ukBAThk3', + 'value': 500000000}, + 'fd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': + {'address': u'musGZczug3BAbqobmYherywCwL9REgNaNm', + 'value': 500000000}} + taker.cjamount = amount + taker.input_utxos = {'f34b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': + {'address': u'n31WD8pkfAjg2APV78GnbDTdZb1QonBi5D', + 'value': 10000000}} + if failquery: + jm_single().bc_interface.setQUSFail(True) + taker.make_commitment() + clean_up() + +def test_not_found_maker_utxos(createcmtdata): + taker = get_taker([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]) + orderbook = copy.deepcopy(t_orderbook) + res = taker.initialize(orderbook) + taker.orderbook = copy.deepcopy(t_chosen_orders) #total_cjfee unaffected, all same + maker_response = copy.deepcopy(t_maker_response) + jm_single().bc_interface.setQUSFail(True) + res = taker.receive_utxos(maker_response) + assert not res[0] + assert res[1] == "Not enough counterparties responded to fill, giving up" + jm_single().bc_interface.setQUSFail(False) + +def test_auth_pub_not_found(createcmtdata): + taker = get_taker([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]) + orderbook = copy.deepcopy(t_orderbook) + res = taker.initialize(orderbook) + taker.orderbook = copy.deepcopy(t_chosen_orders) #total_cjfee unaffected, all same + maker_response = copy.deepcopy(t_maker_response) + utxos = ["03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1", + "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0", + "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1"] + fake_query_results = [{'value': 200000000, + 'address': "blah", + 'utxo': utxos[i], + 'confirms': 20} for i in range(3)] + jm_single().bc_interface.insert_fake_query_results(fake_query_results) + res = taker.receive_utxos(maker_response) + assert not res[0] + assert res[1] == "Not enough counterparties responded to fill, giving up" + jm_single().bc_interface.insert_fake_query_results(None) + +@pytest.mark.parametrize( + "schedule, highfee, toomuchcoins, minmakers, notauthed, ignored, nocommit", + [ + ([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, None, None), + ([(0, 0, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, None, None), #sweep + #edge case triggers that don't fail + ([(0, 0, 4, "mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf")], False, False, + 2, False, None, None), #sweep rounding error case + ([(0, 199850001, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, None, None), #trigger sub dust change for taker + #edge case triggers that do fail + ([(0, 199850000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, None, None), #trigger negative change + ([(0, 199599800, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, None, None), #trigger sub dust change for maker + ([(0, 20000000, 3, "INTERNAL")], True, False, + 2, False, None, None), #test high fee + ([(0, 20000000, 3, "INTERNAL")], False, False, + 7, False, None, None), #test not enough cp + ([(0, 80000000, 3, "INTERNAL")], False, False, + 2, False, None, "30000"), #test failed commit + ([(0, 20000000, 3, "INTERNAL")], False, False, + 2, True, None, None), #test unauthed response + ([(0, 5000000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, True, + 2, False, None, None), #test too much coins + ([(0, 0, 5, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")], False, False, + 2, False, ["J559UPUSLLjHJpaB", "J55z23xdjxJjC7er"], None), #test inadequate for sweep + ]) +def test_taker_init(createcmtdata, schedule, highfee, toomuchcoins, minmakers, + notauthed, ignored, nocommit): + #these tests do not trigger utxo_retries + oldtakerutxoretries = jm_single().config.get("POLICY", "taker_utxo_retries") + oldtakerutxoamtpercent = jm_single().config.get("POLICY", "taker_utxo_amtpercent") + jm_single().config.set("POLICY", "taker_utxo_retries", "20") + def clean_up(): + jm_single().config.set("POLICY", "minimum_makers", oldminmakers) + jm_single().config.set("POLICY", "taker_utxo_retries", oldtakerutxoretries) + jm_single().config.set("POLICY", "taker_utxo_amtpercent", oldtakerutxoamtpercent) + oldminmakers = jm_single().config.get("POLICY", "minimum_makers") + jm_single().config.set("POLICY", "minimum_makers", str(minmakers)) + taker = get_taker(schedule) + orderbook = copy.deepcopy(t_orderbook) + if highfee: + for o in orderbook: + #trigger high-fee warning; but reset in next step + o['cjfee'] = '1.0' + if ignored: + taker.ignored_makers = ignored + if nocommit: + jm_single().config.set("POLICY", "taker_utxo_amtpercent", nocommit) + res = taker.initialize(orderbook) + if toomuchcoins or ignored: + assert not res[0] + return clean_up() + if nocommit: + print(str(res)) + assert not res[0] + return clean_up() + taker.orderbook = copy.deepcopy(t_chosen_orders) #total_cjfee unaffected, all same + maker_response = copy.deepcopy(t_maker_response) + if notauthed: + #Doctor one of the maker response data fields + maker_response["J559UPUSLLjHJpaB"][1] = "xx" #the auth pub + if schedule[0][1] == 199850000: + #triggers negative change + #makers offer 3000 txfee; we estimate ~ 147*10 + 2*34 + 10=1548 bytes + #times 30k = 46440, so we pay 43440, plus maker fees = 3*0.0002*200000000 + #roughly, gives required selected = amt + 163k, hence the above = + #2btc - 150k sats = 199850000 (tweaked because of aggressive coin selection) + #simulate the effect of a maker giving us a lot more utxos + taker.utxos["dummy_for_negative_change"] = ["a", "b", "c", "d", "e"] + with pytest.raises(ValueError) as e_info: + res = taker.receive_utxos(maker_response) + return clean_up() + if schedule[0][1] == 199850001: + #our own change is greater than zero but less than dust + #use the same edge case as for negative change, don't add dummy inputs + #(because we need tx creation to complete), but trigger case by + #bumping dust threshold + jm_single().BITCOIN_DUST_THRESHOLD = 10000 + res = taker.receive_utxos(maker_response) + #should have succeeded to build tx + assert res[0] + #change should be none + assert not taker.my_change_addr + return clean_up() + if schedule[0][1] == 199599800: + #need to force negative fees to make this feasible + for k, v in taker.orderbook.iteritems(): + v['cjfee'] = '-0.002' + # change_amount = (total_input - self.cjamount - + # self.orderbook[nick]['txfee'] + real_cjfee) + #suppose change amount is 1000 (sub dust), then solve for x; + #given that real_cjfee = -0.002*x + #change = 200000000 - x - 1000 - 0.002*x + #x*1.002 = 1999999000; x = 199599800 + res = taker.receive_utxos(maker_response) + assert not res[0] + assert res[1] == "Not enough counterparties responded to fill, giving up" + return clean_up() + if schedule[0][3] == "mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf": + #to trigger rounding error for sweep (change non-zero), + #modify the total_input via the values in self.input_utxos; + #the amount to trigger a 2 satoshi change is found by trial-error. + #TODO note this test is not adequate, because the code is not; + #the code does not *DO* anything if a condition is unexpected. + taker.input_utxos = copy.deepcopy(t_utxos_by_mixdepth)[0] + for k,v in taker.input_utxos.iteritems(): + v["value"] = int(0.999805228 * v["value"]) + res = taker.receive_utxos(maker_response) + assert res[0] + return clean_up() + + res = taker.receive_utxos(maker_response) + if minmakers != 2: + assert not res[0] + assert res[1] == "Not enough counterparties responded to fill, giving up" + return clean_up() + + assert res[0] + #re-calling will trigger "finished" code, since schedule is "complete". + res = taker.initialize(orderbook) + assert not res[0] + + #some exception cases: no coinjoin address, no change address: + #donations not yet implemented: + taker.my_cj_addr = None + with pytest.raises(NotImplementedError) as e_info: + taker.prepare_my_bitcoin_data() + with pytest.raises(NotImplementedError) as e_info: + taker.sign_tx("a", "b", "c") + with pytest.raises(NotImplementedError) as e_info: + a = taker.coinjoin_address() + taker.wallet.inject_addr_get_failure = True + taker.my_cj_addr = "dummy" + assert not taker.prepare_my_bitcoin_data() + #clean up + return clean_up() + +@pytest.mark.parametrize( + "schedule_len", + [ + (7), + ]) +def test_unconfirm_confirm(schedule_len): + """These functions are: do-nothing by default (unconfirm, for Taker), + and merely update schedule index for confirm (useful for schedules/tumbles). + This tests that the on_finished callback correctly reports the fromtx + variable as "False" once the schedule is complete. + """ + test_unconfirm_confirm.txflag = True + def finished_for_confirms(res, fromtx=False): + assert res #confirmed should always send true + test_unconfirm_confirm.txflag = fromtx + + taker = get_taker(schedule_len=schedule_len, on_finished=finished_for_confirms) + taker.unconfirm_callback("a", "b") + for i in range(schedule_len-1): + taker.schedule_index += 1 + fromtx = taker.confirm_callback("a", "b", 1) + assert test_unconfirm_confirm.txflag + taker.schedule_index += 1 + fromtx = taker.confirm_callback("a", "b", 1) + assert not test_unconfirm_confirm.txflag + +@pytest.mark.parametrize( + "dummyaddr, signmethod, schedule", + [ + ("mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", None, + [(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]), + ("mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", "wallet", + [(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]), + ]) +def test_on_sig(createcmtdata, dummyaddr, signmethod, schedule): + #plan: create a new transaction with known inputs and dummy outputs; + #then, create a signature with various inputs, pass in in b64 to on_sig. + #in order for it to verify, the DummyBlockchainInterface will have to + #return the right values in query_utxo_set + + #create 2 privkey + utxos that are to be ours + privs = [x*32 + "\x01" for x in [chr(y) for y in range(1,6)]] + utxos = [str(x)*64+":1" for x in range(5)] + fake_query_results = [{'value': 200000000, + 'utxo': utxos[x], + 'address': bitcoin.privkey_to_address(privs[x], False, magicbyte=0x6f), + 'script': bitcoin.mk_pubkey_script( + bitcoin.privkey_to_address(privs[x], False, magicbyte=0x6f)), + 'confirms': 20} for x in range(5)] + + dbci = DummyBlockchainInterface() + dbci.insert_fake_query_results(fake_query_results) + jm_single().bc_interface = dbci + #make a transaction with all the fake results above, and some outputs + outs = [{'value': 100000000, 'address': dummyaddr}, + {'value': 899990000, 'address': dummyaddr}] + tx = bitcoin.mktx(utxos, outs) + + de_tx = bitcoin.deserialize(tx) + #prepare the Taker with the right intermediate data + taker = get_taker(schedule=schedule, sign_method=signmethod) + taker.nonrespondants=["cp1", "cp2", "cp3"] + taker.latest_tx = de_tx + #my inputs are the first 2 utxos + taker.input_utxos = {utxos[0]: + {'address': bitcoin.privkey_to_address(privs[0], False, magicbyte=0x6f), + 'value': 200000000}, + utxos[1]: + {'address': bitcoin.privkey_to_address(privs[1], False, magicbyte=0x6f), + 'value': 200000000}} + taker.utxos = {None: utxos[:2], "cp1": [utxos[2]], "cp2": [utxos[3]], "cp3":[utxos[4]]} + for i in range(2): + # placeholders required for my inputs + taker.latest_tx['ins'][i]['script'] = 'deadbeef' + #to prepare for my signing, need to mark cjaddr: + taker.my_cj_addr = dummyaddr + #make signatures for the last 3 fake utxos, considered as "not ours": + tx3 = bitcoin.sign(tx, 2, privs[2]) + sig3 = b64encode(bitcoin.deserialize(tx3)['ins'][2]['script'].decode('hex')) + taker.on_sig("cp1", sig3) + tx4 = bitcoin.sign(tx, 3, privs[3]) + sig4 = b64encode(bitcoin.deserialize(tx4)['ins'][3]['script'].decode('hex')) + taker.on_sig("cp2", sig4) + tx5 = bitcoin.sign(tx, 4, privs[4]) + #Before completing with the final signature, which will trigger our own + #signing, try with an injected failure of query utxo set, which should + #prevent this signature being accepted. + dbci.setQUSFail(True) + sig5 = b64encode(bitcoin.deserialize(tx5)['ins'][4]['script'].decode('hex')) + taker.on_sig("cp3", sig5) + #allow it to succeed, and try again + dbci.setQUSFail(False) + #this should succeed and trigger the we-sign code + taker.on_sig("cp3", sig5) + +@pytest.mark.parametrize( + "schedule", + [ + ([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw")]), + ]) +def test_auth_counterparty(schedule): + taker = get_taker(schedule=schedule) + first_maker_response = t_maker_response["J559UPUSLLjHJpaB"] + utxo, auth_pub, cjaddr, changeaddr, sig, maker_pub = first_maker_response + auth_pub_tweaked = auth_pub[:8] + auth_pub[6:8] + auth_pub[10:] + sig_tweaked = sig[:8] + sig[6:8] + sig[10:] + assert taker.auth_counterparty(sig, auth_pub, maker_pub) + assert not taker.auth_counterparty(sig, auth_pub_tweaked, maker_pub) + assert not taker.auth_counterparty(sig_tweaked, auth_pub, maker_pub) + +@pytest.fixture(scope="module") +def createcmtdata(request): + def cmtdatateardown(): + shutil.rmtree("cmtdata") + request.addfinalizer(cmtdatateardown) + if not os.path.exists("cmtdata"): + os.makedirs("cmtdata") + load_program_config() + jm_single().bc_interface = DummyBlockchainInterface() + jm_single().config.set("BLOCKCHAIN", "network", "testnet") + + + + \ No newline at end of file diff --git a/jmdaemon/test/test_enc_wrapper.py b/jmdaemon/test/test_enc_wrapper.py index c62a12d..bd3e940 100644 --- a/jmdaemon/test/test_enc_wrapper.py +++ b/jmdaemon/test/test_enc_wrapper.py @@ -4,7 +4,8 @@ import random import pytest -from joinmarket import init_keypair, get_pubkey, init_pubkey, as_init_encryption, NaclError +from jmdaemon import (init_keypair, get_pubkey, init_pubkey, as_init_encryption, + NaclError) @pytest.mark.parametrize("ab_message,ba_message,num_iterations", diff --git a/scripts/sendpayment.py b/scripts/sendpayment.py index a7945d8..5f16f94 100644 --- a/scripts/sendpayment.py +++ b/scripts/sendpayment.py @@ -236,6 +236,27 @@ def main(): wallet = BitcoinCoreWallet(fromaccount=wallet_name) sync_wallet(wallet, fast=options.fastsync) + def filter_orders_callback(orders_fees, cjamount): + orders, total_cj_fee = orders_fees + jlog.info("Chose these orders: " +pprint.pformat(orders)) + jlog.info('total cj fee = ' + str(total_cj_fee)) + total_fee_pc = 1.0 * total_cj_fee / cjamount + jlog.info('total coinjoin fee = ' + str(float('%.3g' % ( + 100.0 * total_fee_pc))) + '%') + WARNING_THRESHOLD = 0.02 # 2% + if total_fee_pc > WARNING_THRESHOLD: + jlog.info('\n'.join(['=' * 60] * 3)) + jlog.info('WARNING ' * 6) + jlog.info('\n'.join(['=' * 60] * 1)) + jlog.info('OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.') + jlog.info('\n'.join(['=' * 60] * 1)) + jlog.info('WARNING ' * 6) + jlog.info('\n'.join(['=' * 60] * 3)) + if not options.answeryes: + if raw_input('send with these orders? (y/n):')[0] != 'y': + return False + return True + def taker_finished(res, fromtx=False): if fromtx: if res: @@ -256,9 +277,8 @@ def main(): jm_single().bc_interface.tick_forward_chain_interval = 10 taker = Taker(wallet, schedule, - options.answeryes, order_chooser=chooseOrdersFunc, - callbacks=(None, None, taker_finished)) + callbacks=(filter_orders_callback, None, taker_finished)) clientfactory = JMTakerClientProtocolFactory(taker) start_reactor("localhost", options.daemonport, clientfactory) diff --git a/jmclient/test/randomfunc_test.py b/test/randomfunc_test.py similarity index 100% rename from jmclient/test/randomfunc_test.py rename to test/randomfunc_test.py