15 changed files with 1296 additions and 27 deletions
@ -0,0 +1,69 @@ |
|||||||
|
from __future__ import absolute_import, print_function |
||||||
|
|
||||||
|
import base64 |
||||||
|
import random |
||||||
|
import socket |
||||||
|
import ssl |
||||||
|
import threading |
||||||
|
import time |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from jmdaemon.message_channel import MessageChannel |
||||||
|
from jmdaemon.protocol import * |
||||||
|
from jmclient import get_log |
||||||
|
from msgdata import * |
||||||
|
|
||||||
|
log = get_log() |
||||||
|
|
||||||
|
# handle one channel at a time |
||||||
|
class DummyMessageChannel(MessageChannel): |
||||||
|
|
||||||
|
def __init__(self, |
||||||
|
configdata, |
||||||
|
username='username', |
||||||
|
realname='realname', |
||||||
|
password=None, |
||||||
|
daemon=None, |
||||||
|
hostid=None): |
||||||
|
MessageChannel.__init__(self, daemon=daemon) |
||||||
|
self.give_up = False |
||||||
|
self.counterparties = [x['counterparty'] for x in t_orderbook] |
||||||
|
self.hostid = "dummy" |
||||||
|
if hostid: |
||||||
|
self.hostid = hostid |
||||||
|
self.serverport = self.hostid |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self.hostid |
||||||
|
|
||||||
|
def run(self): |
||||||
|
"""Simplest possible event loop.""" |
||||||
|
i = 0 |
||||||
|
while True: |
||||||
|
if self.give_up: |
||||||
|
break |
||||||
|
time.sleep(0.5) |
||||||
|
if i == 1: |
||||||
|
if self.on_welcome: |
||||||
|
log.debug("Calling on welcome") |
||||||
|
self.on_welcome(self) |
||||||
|
i += 1 |
||||||
|
|
||||||
|
def shutdown(self): |
||||||
|
self.give_up = True |
||||||
|
|
||||||
|
def close(self): |
||||||
|
self.shutdown() |
||||||
|
|
||||||
|
def _pubmsg(self, msg): |
||||||
|
pass |
||||||
|
def _privmsg(self, nick, cmd, message): |
||||||
|
"""As for pubmsg |
||||||
|
""" |
||||||
|
pass |
||||||
|
def _announce_orders(self, orderlist): |
||||||
|
pass |
||||||
|
def change_nick(self, new_nick): |
||||||
|
print("Changing nick supposedly") |
||||||
|
|
||||||
@ -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" |
||||||
|
}}} |
||||||
@ -0,0 +1,318 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import absolute_import |
||||||
|
'''test daemon-protocol interfacae.''' |
||||||
|
|
||||||
|
import pytest |
||||||
|
from jmdaemon import (JMDaemonServerProtocolFactory, MessageChannelCollection) |
||||||
|
from jmdaemon.orderbookwatch import OrderbookWatch |
||||||
|
from jmdaemon.daemon_protocol import JMDaemonServerProtocol |
||||||
|
from jmdaemon.protocol import (COMMAND_PREFIX, ORDER_KEYS, NICK_HASH_LENGTH, |
||||||
|
NICK_MAX_ENCODED, JM_VERSION, JOINMARKET_NICK_HEADER) |
||||||
|
from jmclient import (load_program_config, get_log, jm_single, get_irc_mchannels, |
||||||
|
JMTakerClientProtocolFactory, Taker, AbstractWallet) |
||||||
|
import os |
||||||
|
from twisted.python.log import startLogging, err |
||||||
|
from twisted.python.log import msg as tmsg |
||||||
|
from twisted.internet import protocol, reactor, task |
||||||
|
from twisted.internet.protocol import ServerFactory, ClientCreator |
||||||
|
from twisted.internet.error import (ConnectionLost, ConnectionAborted, |
||||||
|
ConnectionClosed, ConnectionDone) |
||||||
|
from twisted.protocols.amp import UnknownRemoteError |
||||||
|
from twisted.python import failure |
||||||
|
from twisted.protocols import amp |
||||||
|
from twisted.trial import unittest |
||||||
|
from jmbase.commands import * |
||||||
|
from msgdata import * |
||||||
|
import json |
||||||
|
import time |
||||||
|
import base64 |
||||||
|
from dummy_mc import DummyMessageChannel |
||||||
|
test_completed = False |
||||||
|
end_early = False |
||||||
|
jlog = get_log() |
||||||
|
|
||||||
|
class JMProtocolError(Exception): |
||||||
|
pass |
||||||
|
|
||||||
|
class JMBaseProtocol(amp.AMP): |
||||||
|
def checkClientResponse(self, response): |
||||||
|
"""A generic check of client acceptance; any failure |
||||||
|
is considered criticial. |
||||||
|
""" |
||||||
|
if 'accepted' not in response or not response['accepted']: |
||||||
|
reactor.stop() |
||||||
|
|
||||||
|
def defaultErrback(self, failure): |
||||||
|
failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone, |
||||||
|
ConnectionLost, UnknownRemoteError) |
||||||
|
reactor.stop() |
||||||
|
|
||||||
|
def defaultCallbacks(self, d): |
||||||
|
d.addCallback(self.checkClientResponse) |
||||||
|
d.addErrback(self.defaultErrback) |
||||||
|
|
||||||
|
class JMTestClientProtocol(JMBaseProtocol): |
||||||
|
|
||||||
|
def connectionMade(self): |
||||||
|
self.clientStart() |
||||||
|
|
||||||
|
def clientStart(self): |
||||||
|
self.sigs_received = 0 |
||||||
|
irc = get_irc_mchannels() |
||||||
|
d = self.callRemote(JMInit, |
||||||
|
bcsource="dummyblockchain", |
||||||
|
network="dummynetwork", |
||||||
|
irc_configs=json.dumps(irc), |
||||||
|
minmakers=2, |
||||||
|
maker_timeout_sec=3) |
||||||
|
self.defaultCallbacks(d) |
||||||
|
|
||||||
|
@JMInitProto.responder |
||||||
|
def on_JM_INIT_PROTO(self, nick_hash_length, nick_max_encoded, |
||||||
|
joinmarket_nick_header, joinmarket_version): |
||||||
|
show_receipt("JMINITPROTO", nick_hash_length, nick_max_encoded, |
||||||
|
joinmarket_nick_header, joinmarket_version) |
||||||
|
d = self.callRemote(JMStartMC, |
||||||
|
nick="dummynick") |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMUp.responder |
||||||
|
def on_JM_UP(self): |
||||||
|
show_receipt("JMUP") |
||||||
|
d = self.callRemote(JMSetup, |
||||||
|
role="TAKER", |
||||||
|
n_counterparties=4) #TODO this number should be set |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMSetupDone.responder |
||||||
|
def on_JM_SETUP_DONE(self): |
||||||
|
show_receipt("JMSETUPDONE") |
||||||
|
d = self.callRemote(JMRequestOffers) |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMFillResponse.responder |
||||||
|
def on_JM_FILL_RESPONSE(self, success, ioauth_data): |
||||||
|
show_receipt("JMFILLRESPONSE", success, ioauth_data) |
||||||
|
reactor.callLater(1, self.maketx, ioauth_data) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
def maketx(self, ioauth_data): |
||||||
|
ioauth_data = json.loads(ioauth_data) |
||||||
|
nl = ioauth_data.keys() |
||||||
|
d = self.callRemote(JMMakeTx, |
||||||
|
nick_list= json.dumps(nl), |
||||||
|
txhex="deadbeef") |
||||||
|
self.defaultCallbacks(d) |
||||||
|
|
||||||
|
@JMOffers.responder |
||||||
|
def on_JM_OFFERS(self, orderbook): |
||||||
|
if end_early: |
||||||
|
return {'accepted': True} |
||||||
|
jlog.debug("JMOFFERS" + str(orderbook)) |
||||||
|
#Trigger receipt of verified privmsgs, including unverified |
||||||
|
nick = str(t_chosen_orders.keys()[0]) |
||||||
|
b64tx = base64.b64encode("deadbeef") |
||||||
|
d1 = self.callRemote(JMMsgSignatureVerify, |
||||||
|
verif_result=True, |
||||||
|
nick=nick, |
||||||
|
fullmsg="!push " + b64tx + " abc def", |
||||||
|
hostid="dummy") |
||||||
|
self.defaultCallbacks(d1) |
||||||
|
#unverified |
||||||
|
d2 = self.callRemote(JMMsgSignatureVerify, |
||||||
|
verif_result=False, |
||||||
|
nick=nick, |
||||||
|
fullmsg="!push " + b64tx + " abc def", |
||||||
|
hostid="dummy") |
||||||
|
self.defaultCallbacks(d2) |
||||||
|
d = self.callRemote(JMFill, |
||||||
|
amount=100, |
||||||
|
commitment="dummycommitment", |
||||||
|
revelation="dummyrevelation", |
||||||
|
filled_offers=json.dumps(t_chosen_orders)) |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMSigReceived.responder |
||||||
|
def on_JM_SIG_RECEIVED(self, nick, sig): |
||||||
|
show_receipt("JMSIGRECEIVED", nick, sig) |
||||||
|
self.sigs_received += 1 |
||||||
|
if self.sigs_received == 3: |
||||||
|
#end of test |
||||||
|
reactor.callLater(1, end_test) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMRequestMsgSig.responder |
||||||
|
def on_JM_REQUEST_MSGSIG(self, nick, cmd, msg, msg_to_be_signed, hostid): |
||||||
|
show_receipt("JMREQUESTMSGSIG", nick, cmd, msg, msg_to_be_signed, hostid) |
||||||
|
d = self.callRemote(JMMsgSignature, |
||||||
|
nick=nick, |
||||||
|
cmd=cmd, |
||||||
|
msg_to_return="xxxcreatedsigxx", |
||||||
|
hostid=hostid) |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
@JMRequestMsgSigVerify.responder |
||||||
|
def on_JM_REQUEST_MSGSIG_VERIFY(self, msg, fullmsg, sig, pubkey, nick, |
||||||
|
hashlen, max_encoded, hostid): |
||||||
|
show_receipt("JMREQUESTMSGSIGVERIFY", msg, fullmsg, sig, pubkey, |
||||||
|
nick, hashlen, max_encoded, hostid) |
||||||
|
d = self.callRemote(JMMsgSignatureVerify, |
||||||
|
verif_result=True, |
||||||
|
nick=nick, |
||||||
|
fullmsg=fullmsg, |
||||||
|
hostid=hostid) |
||||||
|
self.defaultCallbacks(d) |
||||||
|
return {'accepted': True} |
||||||
|
|
||||||
|
class JMTestClientProtocolFactory(protocol.ClientFactory): |
||||||
|
protocol = JMTestClientProtocol |
||||||
|
|
||||||
|
|
||||||
|
def show_receipt(name, *args): |
||||||
|
tmsg("Received msgtype: " + name + ", args: " + ",".join([str(x) for x in args])) |
||||||
|
|
||||||
|
def end_test(): |
||||||
|
global test_completed |
||||||
|
test_completed = True |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class JMDaemonTestServerProtocol(JMDaemonServerProtocol): |
||||||
|
|
||||||
|
def __init__(self, factory): |
||||||
|
super(JMDaemonTestServerProtocol, self).__init__(factory) |
||||||
|
#respondtoioauths should do nothing unless jmstate = 2 |
||||||
|
self.respondToIoauths(True) |
||||||
|
#calling on_JM_MAKE_TX should also do nothing in wrong state |
||||||
|
assert super(JMDaemonTestServerProtocol, self).on_JM_MAKE_TX( |
||||||
|
1, 2) == {'accepted': False} |
||||||
|
#calling on_JM_FILL with negative amount should reject |
||||||
|
assert super(JMDaemonTestServerProtocol, self).on_JM_FILL( |
||||||
|
-1000, 2, 3, 4) == {'accepted': False} |
||||||
|
#checkutxos also does nothing for rejection at the moment |
||||||
|
self.checkUtxosAccepted(False) |
||||||
|
#None should be returned requesting a cryptobox for an unknown cp |
||||||
|
assert self.get_crypto_box_from_nick("notrealcp") == None |
||||||
|
#does nothing yet |
||||||
|
self.on_error() |
||||||
|
|
||||||
|
@JMRequestOffers.responder |
||||||
|
def on_JM_REQUEST_OFFERS(self): |
||||||
|
for o in t_orderbook: |
||||||
|
#counterparty, oid, ordertype, minsize, maxsize,txfee, cjfee): |
||||||
|
self.on_order_seen(o["counterparty"], o["oid"], o["ordertype"], |
||||||
|
o["minsize"], o["maxsize"], |
||||||
|
o["txfee"], o["cjfee"]) |
||||||
|
return super(JMDaemonTestServerProtocol, self).on_JM_REQUEST_OFFERS() |
||||||
|
|
||||||
|
@JMInit.responder |
||||||
|
def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, |
||||||
|
maker_timeout_sec): |
||||||
|
self.maker_timeout_sec = int(maker_timeout_sec) |
||||||
|
self.minmakers = int(minmakers) |
||||||
|
mcs = [DummyMessageChannel(None)] |
||||||
|
self.mcc = MessageChannelCollection(mcs) |
||||||
|
#The following is a hack to get the counterparties marked seen/active; |
||||||
|
#note it must happen before callign set_msgchan for OrderbookWatch |
||||||
|
self.mcc.on_order_seen = None |
||||||
|
for c in [o['counterparty'] for o in t_orderbook]: |
||||||
|
self.mcc.on_order_seen_trigger(mcs[0], c, "a", "b", "c", "d", "e", "f") |
||||||
|
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) |
||||||
|
self.restart_mc_required = True |
||||||
|
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} |
||||||
|
|
||||||
|
@JMFill.responder |
||||||
|
def on_JM_FILL(self, amount, commitment, revelation, filled_offers): |
||||||
|
tmpfo = json.loads(filled_offers) |
||||||
|
dummypub = "073732a7ca60470f709f23c602b2b8a6b1ba62ee8f3f83a61e5484ab5cbf9c3d" |
||||||
|
#trigger invalid on_pubkey conditions |
||||||
|
reactor.callLater(1, self.on_pubkey, "notrealcp", dummypub) |
||||||
|
reactor.callLater(2, self.on_pubkey, tmpfo.keys()[0], dummypub + "deadbeef") |
||||||
|
#trigger invalid on_ioauth condition |
||||||
|
reactor.callLater(2, self.on_ioauth, "notrealcp", 1, 2, 3, 4, 5) |
||||||
|
#trigger msg sig verify request operation for a dummy message |
||||||
|
#currently a pass-through |
||||||
|
reactor.callLater(1, self.request_signature_verify, "1", |
||||||
|
"!push abcd abc def", "3", "4", |
||||||
|
str(tmpfo.keys()[0]), 6, 7, self.mcc.mchannels[0].hostid) |
||||||
|
#send "valid" onpubkey, onioauth messages |
||||||
|
for k, v in tmpfo.iteritems(): |
||||||
|
reactor.callLater(1, self.on_pubkey, k, dummypub) |
||||||
|
reactor.callLater(2, self.on_ioauth, k, ['a', 'b'], "auth_pub", |
||||||
|
"cj_addr", "change_addr", "btc_sig") |
||||||
|
return super(JMDaemonTestServerProtocol, self).on_JM_FILL(amount, |
||||||
|
commitment, revelation, filled_offers) |
||||||
|
|
||||||
|
@JMMakeTx.responder |
||||||
|
def on_JM_MAKE_TX(self, nick_list, txhex): |
||||||
|
for n in nick_list: |
||||||
|
reactor.callLater(1, self.on_sig, n, "dummytxsig") |
||||||
|
return super(JMDaemonTestServerProtocol, self).on_JM_MAKE_TX(nick_list, |
||||||
|
txhex) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class JMDaemonTestServerProtocolFactory(ServerFactory): |
||||||
|
protocol = JMDaemonTestServerProtocol |
||||||
|
|
||||||
|
def buildProtocol(self, addr): |
||||||
|
return JMDaemonTestServerProtocol(self) |
||||||
|
|
||||||
|
|
||||||
|
class TrialTestJMDaemonProto(unittest.TestCase): |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
load_program_config() |
||||||
|
jm_single().maker_timeout_sec = 1 |
||||||
|
self.port = reactor.listenTCP(27184, JMDaemonTestServerProtocolFactory()) |
||||||
|
self.addCleanup(self.port.stopListening) |
||||||
|
clientconn = reactor.connectTCP("localhost", 27184, |
||||||
|
JMTestClientProtocolFactory()) |
||||||
|
self.addCleanup(clientconn.disconnect) |
||||||
|
print("Got here") |
||||||
|
|
||||||
|
def test_waiter(self): |
||||||
|
print("test_main()") |
||||||
|
return task.deferLater(reactor, 12, self._called_by_deffered) |
||||||
|
|
||||||
|
def _called_by_deffered(self): |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class TestJMDaemonProtoInit(unittest.TestCase): |
||||||
|
|
||||||
|
def setUp(self): |
||||||
|
global end_early |
||||||
|
end_early = True |
||||||
|
print("setUp()") |
||||||
|
load_program_config() |
||||||
|
jm_single().maker_timeout_sec = 1 |
||||||
|
self.port = reactor.listenTCP(27184, JMDaemonServerProtocolFactory()) |
||||||
|
self.addCleanup(self.port.stopListening) |
||||||
|
clientconn = reactor.connectTCP("localhost", 27184, |
||||||
|
JMTestClientProtocolFactory()) |
||||||
|
self.addCleanup(clientconn.disconnect) |
||||||
|
print("Got here") |
||||||
|
|
||||||
|
def test_waiter(self): |
||||||
|
print("test_main()") |
||||||
|
return task.deferLater(reactor, 5, self._called_by_deffered) |
||||||
|
|
||||||
|
def _called_by_deffered(self): |
||||||
|
global end_early |
||||||
|
end_early = False |
||||||
@ -0,0 +1,154 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import absolute_import |
||||||
|
'''Tests of joinmarket bots end-to-end (including IRC and bitcoin) ''' |
||||||
|
|
||||||
|
import subprocess |
||||||
|
import signal |
||||||
|
import os |
||||||
|
import pytest |
||||||
|
import time |
||||||
|
import threading |
||||||
|
import hashlib |
||||||
|
import jmbitcoin as btc |
||||||
|
from jmdaemon import (JOINMARKET_NICK_HEADER, NICK_HASH_LENGTH, |
||||||
|
NICK_MAX_ENCODED, IRCMessageChannel) |
||||||
|
from jmdaemon.message_channel import CJPeerError |
||||||
|
import jmdaemon |
||||||
|
#needed for test framework |
||||||
|
from jmclient import (load_program_config, get_irc_mchannels, jm_single) |
||||||
|
|
||||||
|
python_cmd = "python2" |
||||||
|
yg_cmd = "yield-generator-basic.py" |
||||||
|
yg_name = "ygtest" |
||||||
|
si = 3 |
||||||
|
class DummyDaemon(object): |
||||||
|
def request_signature_verify(self, a, b, c, d, e, |
||||||
|
f, g, h): |
||||||
|
return True |
||||||
|
|
||||||
|
class DummyMC(IRCMessageChannel): |
||||||
|
def __init__(self, configdata, nick, daemon): |
||||||
|
super(DummyMC, self).__init__(configdata, daemon=daemon) |
||||||
|
""" |
||||||
|
#hacked in here to allow auth without mc-collection |
||||||
|
nick_priv = hashlib.sha256(os.urandom(16)).hexdigest() + '01' |
||||||
|
nick_pubkey = btc.privtopub(nick_priv) |
||||||
|
nick_pkh_raw = hashlib.sha256(nick_pubkey).digest()[ |
||||||
|
:NICK_HASH_LENGTH] |
||||||
|
nick_pkh = btc.changebase(nick_pkh_raw, 256, 58) |
||||||
|
#right pad to maximum possible; b58 is not fixed length. |
||||||
|
#Use 'O' as one of the 4 not included chars in base58. |
||||||
|
nick_pkh += 'O' * (NICK_MAX_ENCODED - len(nick_pkh)) |
||||||
|
#The constructed length will be 1 + 1 + NICK_MAX_ENCODED |
||||||
|
nick = JOINMARKET_NICK_HEADER + str( |
||||||
|
jm_single().JM_VERSION) + nick_pkh |
||||||
|
jm_single().nickname = nick |
||||||
|
""" |
||||||
|
self.daemon = daemon |
||||||
|
self.set_nick(nick) |
||||||
|
|
||||||
|
def on_connect(x): |
||||||
|
print('simulated on-connect') |
||||||
|
def on_welcome(x): |
||||||
|
print('simulated on-welcome') |
||||||
|
def on_disconnect(x): |
||||||
|
print('simulated on-disconnect') |
||||||
|
|
||||||
|
def on_order_seen(dummy, counterparty, oid, ordertype, minsize, |
||||||
|
maxsize, txfee, cjfee): |
||||||
|
global yg_name |
||||||
|
yg_name = counterparty |
||||||
|
|
||||||
|
def on_pubkey(pubkey): |
||||||
|
print "received pubkey: " + pubkey |
||||||
|
|
||||||
|
class RawIRCThread(threading.Thread): |
||||||
|
|
||||||
|
def __init__(self, ircmsgchan): |
||||||
|
threading.Thread.__init__(self, name='RawIRCThread') |
||||||
|
self.daemon = True |
||||||
|
self.ircmsgchan = ircmsgchan |
||||||
|
|
||||||
|
def run(self): |
||||||
|
self.ircmsgchan.run() |
||||||
|
|
||||||
|
def test_junk_messages(setup_messaging): |
||||||
|
#start a yg bot just to receive messages |
||||||
|
""" |
||||||
|
wallets = make_wallets(1, |
||||||
|
wallet_structures=[[1,0,0,0,0]], |
||||||
|
mean_amt=1) |
||||||
|
wallet = wallets[0]['wallet'] |
||||||
|
ygp = local_command([python_cmd, yg_cmd,\ |
||||||
|
str(wallets[0]['seed'])], bg=True) |
||||||
|
""" |
||||||
|
#time.sleep(90) |
||||||
|
#start a raw IRCMessageChannel instance in a thread; |
||||||
|
#then call send_* on it with various errant messages |
||||||
|
dm = DummyDaemon() |
||||||
|
mc = DummyMC(get_irc_mchannels()[0], "irc_ping_test", dm) |
||||||
|
mc.register_orderbookwatch_callbacks(on_order_seen=on_order_seen) |
||||||
|
mc.register_taker_callbacks(on_pubkey=on_pubkey) |
||||||
|
mc.on_connect = on_connect |
||||||
|
mc.on_disconnect = on_disconnect |
||||||
|
mc.on_welcome = on_welcome |
||||||
|
RawIRCThread(mc).start() |
||||||
|
#start up a fake counterparty |
||||||
|
mc2 = DummyMC(get_irc_mchannels()[0], yg_name, dm) |
||||||
|
RawIRCThread(mc2).start() |
||||||
|
time.sleep(si) |
||||||
|
mc.request_orderbook() |
||||||
|
time.sleep(si) |
||||||
|
#now try directly |
||||||
|
mc.pubmsg("!orderbook") |
||||||
|
time.sleep(si) |
||||||
|
#should be ignored; can we check? |
||||||
|
mc.pubmsg("!orderbook!orderbook") |
||||||
|
time.sleep(si) |
||||||
|
#assuming MAX_PRIVMSG_LEN is not something crazy |
||||||
|
#big like 550, this should fail |
||||||
|
with pytest.raises(AssertionError) as e_info: |
||||||
|
mc.pubmsg("junk and crap"*40) |
||||||
|
time.sleep(si) |
||||||
|
#assuming MAX_PRIVMSG_LEN is not something crazy |
||||||
|
#small like 180, this should succeed |
||||||
|
mc.pubmsg("junk and crap"*15) |
||||||
|
time.sleep(si) |
||||||
|
#try a long order announcement in public |
||||||
|
#because we don't want to build a real orderbook, |
||||||
|
#call the underlying IRC announce function. |
||||||
|
#TODO: how to test that the sent format was correct? |
||||||
|
mc._announce_orders(["!abc def gh 0001"]*30) |
||||||
|
time.sleep(si) |
||||||
|
#send a fill with an invalid pubkey to the existing yg; |
||||||
|
#this should trigger a NaclError but should NOT kill it. |
||||||
|
mc._privmsg(yg_name, "fill", "0 10000000 abcdef") |
||||||
|
#Test that null privmsg does not cause crash; TODO check maker log? |
||||||
|
mc.send_raw("PRIVMSG " + yg_name + " :") |
||||||
|
time.sleep(si) |
||||||
|
#Try with ob flag |
||||||
|
mc._pubmsg("!reloffer stuff") |
||||||
|
time.sleep(si) |
||||||
|
#Trigger throttling with large messages |
||||||
|
mc._privmsg(yg_name, "tx", "aa"*5000) |
||||||
|
time.sleep(si) |
||||||
|
#with pytest.raises(CJPeerError) as e_info: |
||||||
|
mc.send_error(yg_name, "fly you fools!") |
||||||
|
time.sleep(si) |
||||||
|
#Test the effect of shutting down the connection |
||||||
|
mc.set_reconnect_interval(si-1) |
||||||
|
mc.close() |
||||||
|
mc._announce_orders(["!abc def gh 0001"]*30) |
||||||
|
time.sleep(si+2) |
||||||
|
#kill the connection at socket level |
||||||
|
mc.shutdown() |
||||||
|
|
||||||
|
@pytest.fixture(scope="module") |
||||||
|
def setup_messaging(): |
||||||
|
#Trigger PING LAG sending artificially |
||||||
|
jmdaemon.irc.PING_INTERVAL = 3 |
||||||
|
load_program_config() |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,374 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import absolute_import |
||||||
|
'''test messagechannel management code.''' |
||||||
|
|
||||||
|
import pytest |
||||||
|
from jmdaemon import (JMDaemonServerProtocolFactory, MessageChannelCollection) |
||||||
|
from jmdaemon.message_channel import MChannelThread |
||||||
|
from jmdaemon.orderbookwatch import OrderbookWatch |
||||||
|
from jmdaemon.daemon_protocol import JMDaemonServerProtocol |
||||||
|
from jmdaemon.protocol import (COMMAND_PREFIX, ORDER_KEYS, NICK_HASH_LENGTH, |
||||||
|
NICK_MAX_ENCODED, JM_VERSION, JOINMARKET_NICK_HEADER) |
||||||
|
from jmclient import (load_program_config, get_log, jm_single, get_irc_mchannels, |
||||||
|
JMTakerClientProtocolFactory, Taker, AbstractWallet) |
||||||
|
import os |
||||||
|
from jmbase.commands import * |
||||||
|
from msgdata import * |
||||||
|
import json |
||||||
|
import time |
||||||
|
import hashlib |
||||||
|
import base64 |
||||||
|
import traceback |
||||||
|
import threading |
||||||
|
import jmbitcoin as bitcoin |
||||||
|
from dummy_mc import DummyMessageChannel |
||||||
|
|
||||||
|
jlog = get_log() |
||||||
|
|
||||||
|
def make_valid_nick(i=0): |
||||||
|
nick_priv = hashlib.sha256(chr(i)*16).hexdigest() + '01' |
||||||
|
nick_pubkey = bitcoin.privtopub(nick_priv) |
||||||
|
nick_pkh_raw = hashlib.sha256(nick_pubkey).digest()[:NICK_HASH_LENGTH] |
||||||
|
nick_pkh = bitcoin.changebase(nick_pkh_raw, 256, 58) |
||||||
|
#right pad to maximum possible; b58 is not fixed length. |
||||||
|
#Use 'O' as one of the 4 not included chars in base58. |
||||||
|
nick_pkh += 'O' * (NICK_MAX_ENCODED - len(nick_pkh)) |
||||||
|
#The constructed length will be 1 + 1 + NICK_MAX_ENCODED |
||||||
|
return JOINMARKET_NICK_HEADER + str(JM_VERSION) + nick_pkh |
||||||
|
|
||||||
|
class DummyBox(object): |
||||||
|
def encrypt(self, msg): |
||||||
|
return msg |
||||||
|
def decrypt(self, msg): |
||||||
|
return msg |
||||||
|
|
||||||
|
class DaemonForSigns(object): |
||||||
|
"""The following functions handle requests and responses |
||||||
|
from client for messaging signing and verifying. |
||||||
|
""" |
||||||
|
def __init__(self, mcc): |
||||||
|
self.siglock = threading.Lock() |
||||||
|
self.mcc = mcc |
||||||
|
self.crypto_boxes = {} |
||||||
|
|
||||||
|
def request_signed_message(self, nick, cmd, msg, msg_to_be_signed, hostid): |
||||||
|
with self.siglock: |
||||||
|
#Here we have to pretend we signed it and |
||||||
|
#send it to privmsg |
||||||
|
self.mcc.privmsg(nick, cmd, msg, mc=hostid) |
||||||
|
|
||||||
|
def request_signature_verify(self, msg, fullmsg, sig, pubkey, nick, hashlen, |
||||||
|
max_encoded, hostid): |
||||||
|
with self.siglock: |
||||||
|
#Here we must pretend we verified it and send it to on_verified_privmsg |
||||||
|
self.mcc.on_verified_privmsg(nick, fullmsg, hostid) |
||||||
|
|
||||||
|
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: |
||||||
|
jlog.debug('something wrong, no crypto object, nick=' + nick + |
||||||
|
', message will be dropped') |
||||||
|
return None |
||||||
|
|
||||||
|
def dummy_on_welcome(): |
||||||
|
jlog.debug("On welcome called") |
||||||
|
|
||||||
|
def don_error(): |
||||||
|
jlog.debug("called: " + traceback.extract_stack(None, 2)[0][2]) |
||||||
|
|
||||||
|
def don_ioauth(nick, utxo_list, auth_pub, cj_addr, |
||||||
|
change_addr, btc_sig): |
||||||
|
jlog.debug("onioauth callback") |
||||||
|
jlog.debug("Args are: " + ",".join([str(x) for x in nick, |
||||||
|
utxo_list, auth_pub, cj_addr, |
||||||
|
change_addr, btc_sig])) |
||||||
|
|
||||||
|
def don_sig(nick, sig): |
||||||
|
jlog.debug("calledback on-sig") |
||||||
|
|
||||||
|
don_pubkey = don_sig |
||||||
|
|
||||||
|
|
||||||
|
def don_orderbook_requested(nick, mc): |
||||||
|
jlog.debug("called oobr") |
||||||
|
|
||||||
|
def don_commitment_seen(nick, cmt): |
||||||
|
jlog.debug("called doncommitmentseen") |
||||||
|
jlog.debug("Nick, cmt was: " + str(nick) + " , " + str(cmt)) |
||||||
|
|
||||||
|
def don_seen_auth(nick, cr): |
||||||
|
jlog.debug("called donseen auth") |
||||||
|
jlog.debug("Cr was: " + str(cr)) |
||||||
|
|
||||||
|
def don_push_tx(nick, txhex): |
||||||
|
jlog.debug("called donpushtx with thex: " + str(txhex)) |
||||||
|
|
||||||
|
def don_seen_tx(nick, txhex): |
||||||
|
jlog.debug("called donseentx with txhex: " + str(txhex)) |
||||||
|
|
||||||
|
def don_commitment_transferred(nick, cmt): |
||||||
|
jlog.debug("called doncommitmenttransferred") |
||||||
|
|
||||||
|
def don_order_fill(nick, oid, amount, taker_pk, commit): |
||||||
|
jlog.debug("donorderfill called with: " + ",".join( |
||||||
|
[str(x) for x in [nick, oid, amount, taker_pk, commit]])) |
||||||
|
|
||||||
|
def test_setup_mc(): |
||||||
|
ob = OrderbookWatch() |
||||||
|
ob.on_welcome = dummy_on_welcome |
||||||
|
dmcs = [DummyMessageChannel(None, hostid="hostid"+str(x)) for x in range(3)] |
||||||
|
mcc = MessageChannelCollection(dmcs) |
||||||
|
#this sets orderbookwatch callbacks |
||||||
|
ob.set_msgchan(mcc) |
||||||
|
#we want to set all the callbacks, maker and taker |
||||||
|
mcc.register_taker_callbacks(don_error, don_pubkey, don_ioauth, don_sig) |
||||||
|
mcc.register_maker_callbacks(on_orderbook_requested=don_orderbook_requested, |
||||||
|
on_order_fill=don_order_fill, |
||||||
|
on_seen_auth=don_seen_auth, on_seen_tx=don_seen_tx, |
||||||
|
on_push_tx=don_push_tx, |
||||||
|
on_commitment_seen=don_commitment_seen, |
||||||
|
on_commitment_transferred=don_commitment_transferred) |
||||||
|
mcc.set_nick("testnick") |
||||||
|
dummydaemon = DaemonForSigns(mcc) |
||||||
|
mcc.set_daemon(dummydaemon) |
||||||
|
for mc in dmcs: |
||||||
|
mc.on_welcome(mc) |
||||||
|
#instead of calling mcc.run, we'll start threads for mcs manually so we |
||||||
|
#can probe them |
||||||
|
for mc in dmcs: |
||||||
|
MChannelThread(mc).start() |
||||||
|
for m in dmcs: |
||||||
|
m.on_pubmsg("testmaker", "!orderbook") |
||||||
|
#receive invalid pubmsgs |
||||||
|
for msg in ["!orderbook!orderbook", "!notacommand a b c", "no command prefix", |
||||||
|
"!reloffer 0 4000 5000 100"]: |
||||||
|
dmcs[2].on_pubmsg("testmaker", msg) |
||||||
|
|
||||||
|
mcc.request_orderbook() |
||||||
|
mcc.pubmsg("outward pubmsg") |
||||||
|
#now create a verifiable counterparty nick; |
||||||
|
#to get it into active state, need to receive an orderbook from it |
||||||
|
cp1 = make_valid_nick() |
||||||
|
#Simulate order receipt on 2 of 3 msgchans from this nick; |
||||||
|
#note that it will have its active chan set to mc "1" because that |
||||||
|
#is the last it was seen on: |
||||||
|
dmcs[0].on_privmsg(cp1, "!reloffer 0 4000 5000 100 0.2 abc def") |
||||||
|
dmcs[1].on_privmsg(cp1, "!reloffer 0 4000 5000 100 0.2 abc def") |
||||||
|
time.sleep(0.5) |
||||||
|
#send back a response |
||||||
|
mcc.privmsg(cp1, "fill", "0") |
||||||
|
#trigger failure to find nick in privmsg |
||||||
|
mcc.privmsg(cp1+"XXX", "fill", "0") |
||||||
|
#trigger check_privmsg decorator |
||||||
|
mcc.send_sigs(cp1, ["abc", "def"]) |
||||||
|
mcc.send_pubkey(cp1, "testpubkey") |
||||||
|
mcc.send_ioauth(cp1, ["abc", "def"], "testpub", "testaddr1", "testaddr2", |
||||||
|
"testsig") |
||||||
|
mcc.send_error(cp1, "errormsg") |
||||||
|
mcc.push_tx(cp1, "deadbeef") |
||||||
|
#kill the chan on which the cp is marked active; |
||||||
|
#note dummychannel has no actual shutdown (call it anyway), |
||||||
|
#so change its status manually. |
||||||
|
dmcs[2].shutdown() |
||||||
|
mcc.mc_status[dmcs[1]] = 2 |
||||||
|
time.sleep(0.5) |
||||||
|
#Flush removes references to inactive channels (in this case dmcs[1]). |
||||||
|
#Dynamic switching of cp1 should occur to the other seen channel (dmcs[0]). |
||||||
|
mcc.flush_nicks() |
||||||
|
#force cp1 to be unseen on mc 0: |
||||||
|
mcc.unsee_nick(cp1, dmcs[0]) |
||||||
|
del mcc.active_channels[cp1] |
||||||
|
#try sending a privmsg again; this time it should just print a warning, |
||||||
|
#as cp1 is not seen anywhere |
||||||
|
mcc.send_sigs(cp1, ["abc", "def"]) |
||||||
|
#simulate order cancels (even though we have none) |
||||||
|
mcc.cancel_orders([0,1,2]) |
||||||
|
#let cp1 be seen on mc2 without having got into active channels; |
||||||
|
#note that this is an illegal pubmsg and is ignored for everything *except* |
||||||
|
#nick_seen (what we need here) |
||||||
|
dmcs[2].on_pubmsg(cp1, "random") |
||||||
|
mcc.send_sigs(cp1, ["abc", "def"]) |
||||||
|
#Try using the proper way of setting up privsmgs |
||||||
|
#first try without box |
||||||
|
mcc.prepare_privmsg(cp1, "auth", "a b c") |
||||||
|
dummydaemon.crypto_boxes[cp1] = ["a", DummyBox()] |
||||||
|
#now conditions are correct, should succeed: |
||||||
|
mcc.prepare_privmsg(cp1, "auth", "a b c") |
||||||
|
#try again but this time there is no active channel |
||||||
|
del mcc.active_channels[cp1] |
||||||
|
mcc.prepare_privmsg(cp1, "auth", "a b c") |
||||||
|
#try announcing orders; first public |
||||||
|
mcc.announce_orders(t_orderbook) |
||||||
|
#try on fake mc |
||||||
|
mcc.announce_orders(t_orderbook, new_mc="fakemc") |
||||||
|
#direct to one cp |
||||||
|
mcc.announce_orders(t_orderbook, nick=cp1) |
||||||
|
#direct to one cp on one mc |
||||||
|
mcc.announce_orders(t_orderbook, nick=cp1, new_mc=dmcs[0]) |
||||||
|
|
||||||
|
#Next, set up 6 counterparties and fill their offers, |
||||||
|
#send txs to them |
||||||
|
cps = [make_valid_nick(i) for i in range(1, 7)] |
||||||
|
#reuse t_chosen_orders data, but swap out the counterparty names |
||||||
|
offervals = t_chosen_orders.values() |
||||||
|
new_offers = dict(zip(cps, offervals)) |
||||||
|
#first, pretend they all showed up on all 3 mcs: |
||||||
|
for m in dmcs: |
||||||
|
for cp in cps: |
||||||
|
m.on_privmsg(cp, "!reloffer 0 400000 500000 100 0.002 abc def") |
||||||
|
#next, call main fill function |
||||||
|
mcc.fill_orders(new_offers, 1000, "dummypubkey", "dummycommit") |
||||||
|
#now send a dummy transaction to this same set. |
||||||
|
#first fails with no crypto box. |
||||||
|
mcc.send_tx(cps, "deadbeef") |
||||||
|
#Now initialize the boxes |
||||||
|
for c in cps: |
||||||
|
dummydaemon.crypto_boxes[c] = ["a", DummyBox()] |
||||||
|
mcc.send_tx(cps, "deadbeef") |
||||||
|
#try to send the transaction to a wrong cp: |
||||||
|
mcc.send_tx(["notrealcp"], "deadbeef") |
||||||
|
|
||||||
|
#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[1]] == 2 |
||||||
|
assert mcc.mc_status[dmcs[2]] == 1 |
||||||
|
#simulate re-connection of dmcs[1] ; note that this code isn't used atm |
||||||
|
mcc.on_connect_trigger(dmcs[1]) |
||||||
|
assert mcc.mc_status[dmcs[1]] == 1 |
||||||
|
#Now trigger disconnection code; each mc one by one; the last should trigger |
||||||
|
#on_disconnect callback |
||||||
|
for m in dmcs: |
||||||
|
mcc.on_disconnect_trigger(m) |
||||||
|
#reconnect; effect is all nick references are flushed |
||||||
|
for m in dmcs: |
||||||
|
mcc.on_connect_trigger(m) |
||||||
|
assert mcc.active_channels == {} |
||||||
|
#have the cps rearrive |
||||||
|
for m in dmcs: |
||||||
|
for cp in cps: |
||||||
|
m.on_privmsg(cp, "!reloffer 0 4000 5000 100 0.2 abc def") |
||||||
|
|
||||||
|
##################################################################### |
||||||
|
#next series of messages are to test various normal and abnormal |
||||||
|
#message receipts under normal connection conditions |
||||||
|
##################################################################### |
||||||
|
|
||||||
|
#simulate receipt of commitments |
||||||
|
#valid |
||||||
|
dmcs[0].on_pubmsg(cps[2], "!hp2 deadbeef") |
||||||
|
#invalid missing field |
||||||
|
dmcs[0].on_pubmsg(cps[2], "!hp2") |
||||||
|
#receive commitment via privmsg to trigger commitment_transferred |
||||||
|
dmcs[0].on_privmsg(cps[2], "!hp2 deadbeef abc def") |
||||||
|
#simulate receipt of order cancellation |
||||||
|
#valid |
||||||
|
dmcs[0].on_pubmsg(cps[2], "!cancel 2") |
||||||
|
#invalid oid |
||||||
|
dmcs[0].on_pubmsg(cps[2], "!cancel x") |
||||||
|
#too short privmsg (can't even have a signature) |
||||||
|
dmcs[0].on_privmsg(cps[2], COMMAND_PREFIX) |
||||||
|
#not using correct protocol start character |
||||||
|
dmcs[0].on_privmsg(cps[2], "A B C") |
||||||
|
#unrecognized command |
||||||
|
dmcs[0].on_privmsg(cps[2], "!fakecommand A B C D") |
||||||
|
#Perhaps dubious, but currently msg after command must be non-zero |
||||||
|
dmcs[0].on_privmsg(cps[2], "!reloffer sig1 sig2") |
||||||
|
#Simulating receipt of encrypted messages: |
||||||
|
#ioauth |
||||||
|
dummy_on_ioauth_msg = "deadbeef:0,deadbeef:1 XauthpubX XcjaddrX XchangeaddrX XbtcsigX" |
||||||
|
b64dummyioauth = base64.b64encode(dummy_on_ioauth_msg) |
||||||
|
dmcs[0].on_privmsg(cps[3], "!ioauth " + b64dummyioauth + " sig1 sig2") |
||||||
|
#Try with a garbage b64 (but decodable); should throw index error at least |
||||||
|
dmcs[0].on_privmsg(cps[3], "!ioauth _*_ sig1 sig2") |
||||||
|
#Try also for receipt from an unknown counterparty; should fail with no enc box |
||||||
|
dmcs[0].on_privmsg("notrealcp", "!ioauth " + b64dummyioauth + " sig1 sig2") |
||||||
|
#Try same message from valid cp but with corrupted b64 |
||||||
|
b64dummyioauth = "999" |
||||||
|
dmcs[0].on_privmsg(cps[3], "!ioauth " + b64dummyioauth + " sig1 sig2") |
||||||
|
#sig |
||||||
|
dummy_on_sig_msg = "dummysig" |
||||||
|
b64dummysig = base64.b64encode(dummy_on_sig_msg) |
||||||
|
dmcs[0].on_privmsg(cps[3], "!sig " + b64dummysig + " sig1 sig2") |
||||||
|
#auth |
||||||
|
dummy_auth_msg = "dummyauth" |
||||||
|
b64dummyauth = base64.b64encode(dummy_auth_msg) |
||||||
|
dmcs[0].on_privmsg(cps[2], "!auth " + b64dummyauth + " sig1 sig2") |
||||||
|
#invalid auth (only no message is invalid) |
||||||
|
dmcs[0].on_privmsg(cps[3], "!auth " +base64.b64encode("") + " sig1 sig2") |
||||||
|
#tx |
||||||
|
#valid |
||||||
|
dummy_tx = "deadbeefdeadbeef" |
||||||
|
b64dummytx = base64.b64encode(dummy_tx) |
||||||
|
b642dummytx = base64.b64encode(b64dummytx) |
||||||
|
dmcs[0].on_privmsg(cps[2], "!tx " + b642dummytx + " sig1 sig2") |
||||||
|
badbase64tx = "999" |
||||||
|
badbase64tx2 = base64.b64encode(badbase64tx) |
||||||
|
#invalid txhex; here the first round will work (msg decryption), second shouldn't |
||||||
|
dmcs[0].on_privmsg(cps[2], "!tx " + badbase64tx2 + " sig1 sig2") |
||||||
|
#push |
||||||
|
#valid |
||||||
|
dmcs[0].on_privmsg(cps[2], "!push " + b642dummytx + " sig1 sig2") |
||||||
|
#invalid |
||||||
|
dmcs[0].on_privmsg(cps[2], "!push 999 sig1 sig2") |
||||||
|
#fill |
||||||
|
#valid, no commit |
||||||
|
dmcs[0].on_privmsg(cps[4], "!fill 0 4000 dummypub sig1 sig2") |
||||||
|
#valid with commit |
||||||
|
dmcs[0].on_privmsg(cps[4], "!fill 0 4000 dummypub dummycommit sig1 sig2") |
||||||
|
#invalid length |
||||||
|
dmcs[0].on_privmsg(cps[4], "!fill 0 sig1 sig2") |
||||||
|
#pubkey |
||||||
|
dmcs[0].on_privmsg(cps[4], "!pubkey dummypub sig1 sig2") |
||||||
|
############################################################## |
||||||
|
#End message receipts |
||||||
|
############################################################## |
||||||
|
|
||||||
|
#simulate loss of conncetion to cp[0] |
||||||
|
for m in dmcs[::-1]: |
||||||
|
mcc.on_nick_leave_trigger(cps[0], m) |
||||||
|
#call onnickleave for something not in the ac list |
||||||
|
mcc.on_nick_leave_trigger("notrealcp", dmcs[0]) |
||||||
|
#make mcs 0,1 go down so that when cp[1] tries to dynamic switch, it fails |
||||||
|
mcc.on_disconnect_trigger(dmcs[0]) |
||||||
|
mcc.on_disconnect_trigger(dmcs[1]) |
||||||
|
mcc.on_nick_leave_trigger(cps[1], dmcs[2]) |
||||||
|
mcc.shutdown() |
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"failuretype, mcindex, wait", |
||||||
|
[("shutdown", 0, 1), |
||||||
|
("break", 1, 1), |
||||||
|
("bad", 1, 1), |
||||||
|
]) |
||||||
|
def test_mc_run(failuretype, mcindex, wait): |
||||||
|
ob = OrderbookWatch() |
||||||
|
ob.on_welcome = dummy_on_welcome |
||||||
|
dmcs = [DummyMessageChannel(None, hostid="hostid"+str(x)) for x in range(3)] |
||||||
|
mcc = MessageChannelCollection(dmcs) |
||||||
|
#this sets orderbookwatch callbacks |
||||||
|
ob.set_msgchan(mcc) |
||||||
|
dummydaemon = DaemonForSigns(mcc) |
||||||
|
mcc.set_daemon(dummydaemon) |
||||||
|
#to externally trigger give up condition, start mcc itself in a thread |
||||||
|
MChannelThread(mcc).start() |
||||||
|
time.sleep(0.2) |
||||||
|
mcc.give_up = True |
||||||
|
time.sleep(1.2) |
||||||
|
#wipe state, this time use failure injections |
||||||
|
mcc = MessageChannelCollection(dmcs) |
||||||
|
#to test exception raise on bad failure inject, don't use thread: |
||||||
|
if failuretype == "bad": |
||||||
|
with pytest.raises(NotImplementedError) as e_info: |
||||||
|
mcc.run(failures=[failuretype, mcindex, wait]) |
||||||
|
else: |
||||||
|
#need to override thread run() |
||||||
|
class FIThread(MChannelThread): |
||||||
|
def run(self): |
||||||
|
self.mc.run(failures=self.failures) |
||||||
|
fi = FIThread(mcc) |
||||||
|
fi.failures = [failuretype, mcindex, wait] |
||||||
|
fi.start() |
||||||
|
time.sleep(wait+0.5) |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,129 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
from __future__ import print_function |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from jmdaemon.orderbookwatch import OrderbookWatch |
||||||
|
from jmdaemon import IRCMessageChannel |
||||||
|
from jmclient import get_irc_mchannels, load_program_config |
||||||
|
from jmdaemon.protocol import JM_VERSION, ORDER_KEYS |
||||||
|
class DummyDaemon(object): |
||||||
|
def request_signature_verify(self, a, b, c, d, e, |
||||||
|
f, g, h): |
||||||
|
return True |
||||||
|
|
||||||
|
class DummyMC(IRCMessageChannel): |
||||||
|
def __init__(self, configdata, nick, daemon): |
||||||
|
super(DummyMC, self).__init__(configdata, daemon=daemon) |
||||||
|
self.daemon = daemon |
||||||
|
self.set_nick(nick) |
||||||
|
|
||||||
|
def on_welcome(x): |
||||||
|
print("Simulated on-welcome") |
||||||
|
|
||||||
|
def get_ob(): |
||||||
|
load_program_config() |
||||||
|
dm = DummyDaemon() |
||||||
|
mc = DummyMC(get_irc_mchannels()[0], "test", dm) |
||||||
|
ob = OrderbookWatch() |
||||||
|
ob.on_welcome = on_welcome |
||||||
|
ob.set_msgchan(mc) |
||||||
|
return ob |
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"badtopic", |
||||||
|
[("abc|"), |
||||||
|
("abcd|def"), |
||||||
|
("abc| 0 a qvd"), |
||||||
|
]) |
||||||
|
def test_ob(badtopic): |
||||||
|
ob = get_ob() |
||||||
|
topic = ("JoinMarket open outcry pit. /r/joinmarket Discussion in #joinmarket" |
||||||
|
"| 0 5 LATEST RELEASE v0.2.2. Useful new features. Update ASAP, and " |
||||||
|
"do not use pre-0.2.0! https://bitcointalk.org/index.php?topic=91911" |
||||||
|
"6.msg16714124#msg16714124") |
||||||
|
ob.on_set_topic(topic) |
||||||
|
#should not throw: |
||||||
|
ob.on_set_topic(badtopic) |
||||||
|
#test old version |
||||||
|
future_ver = str(JM_VERSION + 2) |
||||||
|
|
||||||
|
deprecated = topic.replace("| 0 5", "| 0 "+future_ver) |
||||||
|
ob.on_set_topic(deprecated) |
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"counterparty, oid, ordertype, minsize, maxsize, txfee, cjfee, expected", |
||||||
|
[ |
||||||
|
#good absoffer |
||||||
|
("test", "0", "absoffer", "3000", "4000", "2", "300", True), |
||||||
|
#good reloffer |
||||||
|
("test", "0", "reloffer", "3000", "4000", "2", "0.3", True), |
||||||
|
#dusty minsize OK |
||||||
|
("test", "0", "reloffer", "1000", "4000", "2", "0.3", True), |
||||||
|
#invalid oid |
||||||
|
("test", "-2", "reloffer", "3000", "4000", "2", "0.3", False), |
||||||
|
#invalid minsize |
||||||
|
("test", "2", "reloffer", "-3000", "4000", "2", "0.3", False), |
||||||
|
#invalid maxsize |
||||||
|
("test", "2", "reloffer", "3000", "2200000000000000", "2", "0.3", False), |
||||||
|
#invalid txfee |
||||||
|
("test", "2", "reloffer", "3000", "4000", "-1", "0.3", False), |
||||||
|
#min bigger than max |
||||||
|
("test", "2", "reloffer", "4000", "3000", "2", "0.3", False), |
||||||
|
#non-integer absoffer |
||||||
|
("test", "2", "absoffer", "3000", "4000", "2", "0.3", False), |
||||||
|
#invalid syntax for cjfee |
||||||
|
("test", "2", "reloffer", "3000", "4000", "2", "0.-1", False), |
||||||
|
#invalid type for oid |
||||||
|
("test", "xxx", "reloffer", "3000", "4000", "2", "0.3", False), |
||||||
|
]) |
||||||
|
def test_order_seen_cancel(counterparty, oid, ordertype, minsize, maxsize, txfee, |
||||||
|
cjfee, expected): |
||||||
|
ob = get_ob() |
||||||
|
ob.on_order_seen(counterparty, oid, ordertype, minsize, maxsize, |
||||||
|
txfee, cjfee) |
||||||
|
if expected: |
||||||
|
#offer should now be in the orderbook |
||||||
|
rows = ob.db.execute('SELECT * FROM orderbook;').fetchall() |
||||||
|
orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] |
||||||
|
assert len(orderbook) == 1 |
||||||
|
#test it can be removed |
||||||
|
ob.on_order_cancel(counterparty, oid) |
||||||
|
rows = ob.db.execute('SELECT * FROM orderbook;').fetchall() |
||||||
|
orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] |
||||||
|
assert len(orderbook) == 0 |
||||||
|
|
||||||
|
def test_disconnect_leave(): |
||||||
|
ob = get_ob() |
||||||
|
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'}] |
||||||
|
for o in t_orderbook: |
||||||
|
ob.on_order_seen(o['counterparty'], o['oid'], o['ordertype'], |
||||||
|
o['minsize'], o['maxsize'], o['txfee'], o['cjfee']) |
||||||
|
rows = ob.db.execute('SELECT * FROM orderbook;').fetchall() |
||||||
|
orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] |
||||||
|
assert len(orderbook) == 6 |
||||||
|
#simulate one cp leaves: |
||||||
|
ob.on_nick_leave("J5cBx1FwUVh9zzoO") |
||||||
|
rows = ob.db.execute('SELECT * FROM orderbook;').fetchall() |
||||||
|
orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] |
||||||
|
assert len(orderbook) == 5 |
||||||
|
#simulate quit |
||||||
|
ob.on_disconnect() |
||||||
|
rows = ob.db.execute('SELECT * FROM orderbook;').fetchall() |
||||||
|
orderbook = [dict([(k, o[k]) for k in ORDER_KEYS]) for o in rows] |
||||||
|
assert len(orderbook) == 0 |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in new issue