#! /usr/bin/env python from __future__ import absolute_import '''test client-protocol interfacae.''' import pytest from jmclient import (get_schedule, load_program_config, start_reactor, Taker, get_log, JMTakerClientProtocolFactory, jm_single) from jmclient.client_protocol import JMProtocolError, JMTakerClientProtocol import os from twisted.python.log import startLogging, err from twisted.python.log import msg as tmsg from twisted.internet import protocol, reactor 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 jmbase.commands import * from taker_test_data import t_raw_signed_tx import json import time import jmbitcoin as bitcoin test_completed = False clientfactory = None runno = 0 jlog = get_log() class DummyTaker(Taker): def set_fail_init(self, val): self.failinit = val def set_fail_utxos(self, val): self.failutxos = val def default_taker_info_callback(self, infotype, msg): jlog.debug(infotype + ":" + msg) def initialize(self, orderbook): """Once the daemon is active and has returned the current orderbook, select offers, re-initialize variables and prepare a commitment, then send it to the protocol to fill offers. """ if self.failinit==-1: return (True, -1, "aa"*32, {'dummy':'revelation'},orderbook[:2]) elif self.failinit: return (False,) else: return (True, 1000000, "aa"*32, {'dummy':'revelation'}, orderbook[:2]) def receive_utxos(self, ioauth_data): """Triggered when the daemon returns utxo data from makers who responded; this is the completion of phase 1 of the protocol """ if self.failutxos: return (False, "dummyreason") else: return (True, [x*64 + ":01" for x in ["a", "b", "c"]], t_raw_signed_tx) def on_sig(self, nick, sigb64): jlog.debug("We got a sig: " + sigb64) end_test() return True 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) def show_receipt(name, *args): tmsg("Received msgtype: " + name + ", args: " + ",".join([str(x) for x in args])) def end_client(client): client.shutdown_requested = True def end_test(): #global runno global test_completed #runno += 1 #jlog.info("Updated runno to: " + str(runno)) #taker = DummyTaker(None, None) #if runno == 1: # jlog.info("Run number was less than 2") # taker.set_fail_init(True) # taker.set_fail_utxos(False) # cfactory = JMTakerClientProtocolFactory(taker) # reactor.connectTCP("localhost", 27184, cfactory) # return #elif runno == 2: # taker.set_fail_init(False) # taker.set_fail_utxos(True) # cfactory = JMTakerClientProtocolFactory(taker) # reactor.connectTCP("localhost", 27184, cfactory) # return test_completed = True client = clientfactory.getClient() reactor.callLater(2, end_client, client) class JMTestServerProtocol(JMBaseProtocol): @JMInit.responder def on_JM_INIT(self, bcsource, network, irc_configs, minmakers, maker_timeout_sec): show_receipt("JMINIT", bcsource, network, irc_configs, minmakers, maker_timeout_sec) d = self.callRemote(JMInitProto, nick_hash_length=1, nick_max_encoded=2, joinmarket_nick_header="J", joinmarket_version=5) self.defaultCallbacks(d) return {'accepted': True} @JMStartMC.responder def on_JM_START_MC(self, nick): show_receipt("STARTMC", nick) d = self.callRemote(JMUp) self.defaultCallbacks(d) return {'accepted': True} @JMSetup.responder def on_JM_SETUP(self, role, n_counterparties): show_receipt("JMSETUP", role,n_counterparties) d = self.callRemote(JMSetupDone) self.defaultCallbacks(d) return {'accepted': True} @JMRequestOffers.responder def on_JM_REQUEST_OFFERS(self): show_receipt("JMREQUESTOFFERS") #build a huge orderbook to test BigString Argument orderbook = ["aaaa" for _ in range(15)] d = self.callRemote(JMOffers, orderbook=json.dumps(orderbook)) self.defaultCallbacks(d) return {'accepted': True} @JMFill.responder def on_JM_FILL(self, amount, commitment, revelation, filled_offers): success = False if amount == -1 else True show_receipt("JMFILL", amount, commitment, revelation, filled_offers) d = self.callRemote(JMFillResponse, success=success, ioauth_data = json.dumps(['dummy', 'list'])) return {'accepted': True} @JMMakeTx.responder def on_JM_MAKE_TX(self, nick_list, txhex): show_receipt("JMMAKETX", nick_list, txhex) d = self.callRemote(JMSigReceived, nick="dummynick", sig="xxxsig") self.defaultCallbacks(d) #add dummy calls to check message sign and message verify d2 = self.callRemote(JMRequestMsgSig, nick="dummynickforsign", cmd="command1", msg="msgforsign", msg_to_be_signed="fullmsgforsign", hostid="hostid1") self.defaultCallbacks(d2) #To test, this must include a valid ecdsa sig fullmsg = "fullmsgforverify" priv = "aa"*32 + "01" pub = bitcoin.privkey_to_pubkey(priv) sig = bitcoin.ecdsa_sign(fullmsg, priv) d3 = self.callRemote(JMRequestMsgSigVerify, msg="msgforverify", fullmsg=fullmsg, sig=sig, pubkey=pub, nick="dummynickforverify", hashlen=4, max_encoded=5, hostid="hostid2") self.defaultCallbacks(d3) d4 = self.callRemote(JMSigReceived, nick="dummynick", sig="dummysig") self.defaultCallbacks(d4) return {'accepted': True} @JMMsgSignature.responder def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid): show_receipt("JMMSGSIGNATURE", nick, cmd, msg_to_return, hostid) return {'accepted': True} @JMMsgSignatureVerify.responder def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid): show_receipt("JMMSGSIGVERIFY", verif_result, nick, fullmsg, hostid) return {'accepted': True} class JMTestServerProtocolFactory(protocol.ServerFactory): protocol = JMTestServerProtocol class DummyClientProtocolFactory(JMTakerClientProtocolFactory): def buildProtocol(self, addr): return JMTakerClientProtocol(self, self.taker, nick_priv="aa"*32) def test_jm_protocol(): """We cannot use parametrize for different options as we can't run in sequence; hence, parameters hardcoded as lists here """ params = [[False, False], [True, False], [False, True], [-1, False]] global clientfactory load_program_config() jm_single().maker_timeout_sec = 1 reactor.listenTCP(27184, JMTestServerProtocolFactory()) clientfactories = [] takers = [DummyTaker(None, None) for _ in range(len(params))] for i, p in enumerate(params): takers[i].set_fail_init(p[0]) takers[i].set_fail_utxos(p[1]) if i != 0: clientfactories.append(JMTakerClientProtocolFactory(takers[i])) reactor.connectTCP("localhost", 27184, clientfactories[i]) else: clientfactories.append(DummyClientProtocolFactory(takers[i])) clientfactory = clientfactories[0] start_reactor("localhost", 27184, clientfactories[0]) print("Got here") if not test_completed: raise Exception("Failed test")