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