From 270bd7000f6068952c58c953fe14928fd60252d7 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 17:03:17 -0600 Subject: [PATCH 01/25] refactor protocol, put test stubs in place, linting, prep for cli tests --- xcat/cli.py | 60 ++++-- xcat/db.py | 15 +- xcat/protocol.py | 425 +++++++++++++++++++++-------------------- xcat/tests/test_cli.py | 79 ++++---- xcat/tests/utils.py | 28 ++- 5 files changed, 337 insertions(+), 270 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 5b41fc1..aa10367 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -1,29 +1,32 @@ -import argparse, textwrap -from xcat.utils import * +import argparse +import textwrap +import subprocess import xcat.db as db import xcat.userInput as userInput +from xcat.protocol import Protocol from xcat.trades import * -from xcat.protocol import * -import subprocess +from xcat.utils import * + def save_state(trade, tradeid): save(trade) db.create(trade, tradeid) + def checkSellStatus(tradeid): trade = db.get(tradeid) status = seller_check_status(trade) print("Trade status: {0}\n".format(status)) if status == 'init': userInput.authorize_fund_sell(trade) - fund_tx = fund_sell_contract(trade) + fund_tx = protocol.fund_sell_contract(trade) print("Sent fund_tx", fund_tx) trade.sell.fund_tx = fund_tx save_state(trade, tradeid) elif status == 'buyerFunded': secret = db.get_secret(tradeid) print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) - txs = seller_redeem_p2sh(trade, secret) + txs = protocol.seller_redeem_p2sh(trade, secret) if 'redeem_tx' in txs: trade.buy.redeem_tx = txs['redeem_tx'] print("Redeem tx: ", txs['redeem_tx']) @@ -38,9 +41,10 @@ def checkSellStatus(tradeid): elif status == 'sellerRedeemed': print("You have already redeemed the p2sh on the second chain of this trade.") + def buyer_check_status(trade): - sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh) - buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh) + sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) + buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': return 'sellerFunded' # step1 # TODO: Find funding txid. How does buyer get seller redeemed tx? @@ -54,9 +58,10 @@ def buyer_check_status(trade): else: return 'init' + def seller_check_status(trade): - sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh) - buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh) + sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) + buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': return 'sellerFunded' # step1 elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'): @@ -70,6 +75,7 @@ def seller_check_status(trade): else: return 'init' # step0 + def checkBuyStatus(tradeid): trade = db.get(tradeid) status = buyer_check_status(trade) @@ -83,15 +89,17 @@ def checkBuyStatus(tradeid): input("Type 'enter' to allow this program to send funds on your behalf.") print("Trade commitment", trade.commitment) # if verify_p2sh(trade): - fund_tx = fund_contract(trade.buy) + fund_tx = protocol.fund_contract(trade.buy) print("\nYou sent this funding tx: ", fund_tx) trade.buy.fund_tx = fund_tx save_state(trade, tradeid) elif status == 'sellerRedeemed': - secret = find_secret_from_fundtx(trade.buy.currency, trade.buy.p2sh, trade.buy.fund_tx) + secret = protocol.find_secret_from_fundtx(trade.buy.currency, + trade.buy.p2sh, + trade.buy.fund_tx) if secret != None: print("Found secret on blockchain in seller's redeem tx: ", secret) - txs = redeem_p2sh(trade.sell, secret) + txs = protocol.redeem_p2sh(trade.sell, secret) if 'redeem_tx' in txs: trade.sell.redeem_tx = txs['redeem_tx'] print("Redeem txid: ", trade.sell.redeem_tx) @@ -103,14 +111,16 @@ def checkBuyStatus(tradeid): else: print("Secret not found in redeemtx") + # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): trade = x2s(hexstr) trade = db.instantiate(trade) - import_addrs(trade) + protocol.import_addrs(trade) print(trade.toJSON()) save_state(trade, tradeid) + def wormhole_importtrade(): res = subprocess.call('wormhole receive', shell=True) if res == 0: @@ -123,9 +133,10 @@ def wormhole_importtrade(): else: print("Importing trade using magic-wormhole failed.") + # Export a trade by its tradeid def exporttrade(tradeid, wormhole=False): - trade = db.get(tradeid) + trade = db.get(tradeid) hexstr = s2x(trade.toJSON()) if wormhole: tradefile = os.path.join(ROOT_DIR, '.tmp/{0}'.format(tradeid)) @@ -138,20 +149,24 @@ def exporttrade(tradeid, wormhole=False): print(hexstr) return hexstr + def findtrade(tradeid): trade = db.get(tradeid) print(trade.toJSON()) return trade + def find_role(contract): # When regtest created both addrs on same machine, role is both. - if is_myaddr(contract.initiator) and is_myaddr(contract.fulfiller): - return 'test' - elif is_myaddr(contract.initiator): - return 'initiator' + if protocol.is_myaddr(contract.initiator): + if protocol.is_myaddr(contract.fulfiller): + return 'test' + else: + return 'initiator' else: return 'fulfiller' + def checktrade(tradeid): print("In checktrade") trade = db.get(tradeid) @@ -170,22 +185,25 @@ def checktrade(tradeid): role = 'buyer' checkBuyStatus(tradeid) + def newtrade(tradeid, **kwargs): print("Creating new XCAT trade...") erase_trade() - tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf']) + tradeid, trade = protocol.initialize_trade(tradeid, conf=kwargs['conf']) print("Trade", trade) - trade = seller_init(tradeid, trade) + trade = protocol.seller_init(tradeid, trade) print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n") save_state(trade, tradeid) return trade + def listtrades(): print("Trades") trades = db.dump() for trade in trades: print("{0}: {1}".format(trade[0], trade[1])) + def main(): parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=textwrap.dedent('''\ diff --git a/xcat/db.py b/xcat/db.py index c976f45..42fc2d6 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -1,10 +1,10 @@ import plyvel -from xcat.utils import * -import binascii -import sys import json -import ast +# import binascii +# import sys +# import ast from xcat.trades import * +from xcat.utils import * db = plyvel.DB('/tmp/xcatDB', create_if_missing=True) preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) @@ -13,6 +13,7 @@ preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) ######## Trades stored by tradeid ########### ############################################# + # Takes dict or obj, saves json str as bytes def create(trade, tradeid): if type(trade) == dict: @@ -21,6 +22,7 @@ def create(trade, tradeid): trade = trade.toJSON() db.put(b(tradeid), b(trade)) + # Uses the funding txid as the key to save trade def createByFundtx(trade): trade = trade.toJSON() @@ -29,12 +31,14 @@ def createByFundtx(trade): txid = jt['sell']['fund_tx'] db.put(b(txid), b(trade)) + def get(tradeid): rawtrade = db.get(b(tradeid)) tradestr = str(rawtrade, 'utf-8') trade = instantiate(tradestr) return trade + def instantiate(trade): if type(trade) == str: tradestr = json.loads(trade) @@ -45,10 +49,12 @@ def instantiate(trade): ###### Preimages stored by tradeid ########## ############################################# + # Stores secret locally in key/value store by tradeid def save_secret(tradeid, secret): res = preimageDB.put(b(tradeid), b(secret)) + def get_secret(tradeid): secret = preimageDB.get(b(tradeid)) secret = str(secret, 'utf-8') @@ -67,6 +73,7 @@ def dump(): results.append((str(k, 'utf-8'), j)) return results + def print_entries(): it = db.iterator() with db.iterator() as it: diff --git a/xcat/protocol.py b/xcat/protocol.py index 27f8721..f8f10c8 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -1,224 +1,245 @@ -import json -import os, sys -from pprint import pprint -from xcat.utils import * -from xcat.trades import Contract, Trade import xcat.userInput as userInput import xcat.db as db -from xcat.xcatconf import * +import xcat.utils as utils +from xcat.xcatconf import ADDRS +from xcat.trades import Contract, Trade from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy -bitcoinRPC = bitcoinProxy() -zcashRPC = zcashProxy() -def is_myaddr(address): - if address[:1] == 'm': - status = bitcoinRPC.validateaddress(address) - else: - status = zcashRPC.validateaddress(address) - status = status['ismine'] - # print("Address {0} is mine: {1}".format(address, status)) - return status +class Protocol(): -def find_secret_from_fundtx(currency, p2sh, fundtx): - if currency == 'bitcoin': - secret = bitcoinRPC.find_secret(p2sh, fundtx) - else: - secret = zcashRPC.find_secret(p2sh, fundtx) - return secret + def __init__(self): + self.bitcoinRPC = bitcoinProxy() + self.zcashRPC = zcashProxy() -def import_addrs(trade): - check_fund_status(trade.sell.currency, trade.sell.p2sh) - check_fund_status(trade.buy.currency, trade.buy.p2sh) - -def check_p2sh(currency, address): - if currency == 'bitcoin': - print("Checking funds in Bitcoin p2sh") - return bitcoinRPC.check_funds(address) - else: - print("Checking funds in Zcash p2sh") - return zcashRPC.check_funds(address) - -def check_fund_status(currency, address): - if currency == 'bitcoin': - print("Checking funds in Bitcoin p2sh") - return bitcoinRPC.get_fund_status(address) - else: - print("Checking funds in Zcash p2sh") - return zcashRPC.get_fund_status(address) - -# TODO: function to calculate appropriate locktimes between chains -# def verify_p2sh(trade): - # htlc = create_htlc(trade.buy.currency, trade.buy.fulfiller, trade.buy.initiator, trade.commitment, trade.buy.locktime) - # buyer_p2sh = htlc['p2sh'] - # print("Buyer p2sh:", buyer_p2sh) - # If the two p2sh match... - # if buyer_p2sh == trade.buy.p2sh: - # else: - # print("Compiled p2sh for htlc does not match what seller sent.") - -def create_htlc(currency, funder, redeemer, commitment, locktime): - if currency == 'bitcoin': - sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) - else: - sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime) - return sell_p2sh - -def fund_htlc(currency, p2sh, amount): - if currency == 'bitcoin': - txid = bitcoinRPC.fund_htlc(p2sh, amount) - else: - txid = zcashRPC.fund_htlc(p2sh, amount) - return txid - -def fund_contract(contract): - txid = fund_htlc(contract.currency, contract.p2sh, contract.amount) - return txid - -def fund_sell_contract(trade): - sell = trade.sell - txid = fund_htlc(sell.currency, sell.p2sh, sell.amount) - setattr(trade.sell, 'fund_tx', txid) - save(trade) - return txid - -def create_sell_p2sh(trade, commitment, locktime): - # CREATE SELL CONTRACT - sell = trade.sell - contract = create_htlc(sell.currency, sell.initiator, sell.fulfiller, commitment, locktime) - print("sell contract", contract) - setattr(trade.sell, 'p2sh', contract['p2sh']) - setattr(trade.sell, 'redeemScript', contract['redeemScript']) - setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum']) - setattr(trade.buy, 'locktime', contract['locktime']) - save(trade) - -def create_buy_p2sh(trade, commitment, locktime): - ## CREATE BUY CONTRACT - buy = trade.buy - print("\nNow creating buy contract on the {0} blockchain where you will wait for the buyer to send funds...".format(buy.currency)) - buy_contract = create_htlc(buy.currency, buy.fulfiller, buy.initiator, commitment, locktime) - print("Buy contract", buy_contract) - - setattr(trade.buy, 'p2sh', buy_contract['p2sh']) - setattr(trade.buy, 'redeemScript', buy_contract['redeemScript']) - setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum']) - setattr(trade.buy, 'locktime', buy_contract['locktime']) - print("\nNow contact the buyer and tell them to send funds to this p2sh: {0}\n".format(trade.buy.p2sh)) - - save(trade) - -def redeem_p2sh(contract, secret): - currency = contract.currency - if currency == 'bitcoin': - res = bitcoinRPC.redeem_contract(contract, secret) - else: - res = zcashRPC.redeem_contract(contract, secret) - return res - -def parse_secret(chain, txid): - if chain == 'bitcoin': - secret = bitcoinRPC.parse_secret(txid) - else: - secret = zcashRPC.parse_secret(txid) - return secret - -#### Main functions determining user flow from command line -def buyer_redeem(trade): - userInput.authorize_buyer_redeem(trade) - if trade.sell.get_status() == 'redeemed': - print("You already redeemed the funds and acquired {0} {1}".format(trade.sell.amount, trade.sell.currency)) - exit() - else: - # Buyer redeems seller's funded tx - p2sh = trade.sell.p2sh - currency = trade.sell.currency - # Buy contract is where seller disclosed secret in redeeming - if trade.buy.currency == 'bitcoin': - secret = bitcoinRPC.parse_secret(trade.buy.redeem_tx) + def is_myaddr(self, address): + if address[:1] == 'm': + status = self.bitcoinRPC.validateaddress(address) else: - secret = zcashRPC.parse_secret(trade.buy.redeem_tx) - print("Found secret in seller's redeem tx", secret) - redeem_tx = redeem_p2sh(trade.sell, secret) - setattr(trade.sell, 'redeem_tx', redeem_tx) - save(trade) - exit() + status = self.zcashRPC.validateaddress(address) + status = status['ismine'] + # print("Address {0} is mine: {1}".format(address, status)) + return status -def seller_redeem_p2sh(trade, secret): - buy = trade.buy - userInput.authorize_seller_redeem(buy) + def find_secret_from_fundtx(self, currency, p2sh, fundtx): + if currency == 'bitcoin': + secret = self.bitcoinRPC.find_secret(p2sh, fundtx) + else: + secret = self.zcashRPC.find_secret(p2sh, fundtx) + return secret - if trade.sell.get_status() == 'redeemed': - print("You already redeemed the funds and acquired {0} {1}".format(buy.amount, buy.currency)) + def import_addrs(self, trade): + self.check_fund_status(trade.sell.currency, trade.sell.p2sh) + self.check_fund_status(trade.buy.currency, trade.buy.p2sh) + + def check_p2sh(self, currency, address): + if currency == 'bitcoin': + print("Checking funds in Bitcoin p2sh") + return self.bitcoinRPC.check_funds(address) + else: + print("Checking funds in Zcash p2sh") + return self.zcashRPC.check_funds(address) + + def check_fund_status(self, currency, address): + if currency == 'bitcoin': + print("Checking funds in Bitcoin p2sh") + return self.bitcoinRPC.get_fund_status(address) + else: + print("Checking funds in Zcash p2sh") + return self.zcashRPC.get_fund_status(address) + + # TODO: function to calculate appropriate locktimes between chains + # def verify_p2sh(trade): + # htlc = self.create_htlc( + # trade.buy.currency, trade.buy.fulfiller, + # trade.buy.initiator, trade.commitment, + # trade.buy.locktime) + # buyer_p2sh = htlc['p2sh'] + # print("Buyer p2sh:", buyer_p2sh) + # If the two p2sh match... + # if buyer_p2sh == trade.buy.p2sh: + # else: + # print("Compiled p2sh for htlc does not match what seller sent.") + + def create_htlc(self, currency, funder, redeemer, commitment, locktime): + if currency == 'bitcoin': + sell_p2sh = self.bitcoinRPC.hashtimelockcontract( + funder, redeemer, commitment, locktime) + else: + sell_p2sh = self.zcashRPC.hashtimelockcontract( + funder, redeemer, commitment, locktime) + return sell_p2sh + + def fund_htlc(self, currency, p2sh, amount): + if currency == 'bitcoin': + txid = self.bitcoinRPC.fund_htlc(p2sh, amount) + else: + txid = self.zcashRPC.fund_htlc(p2sh, amount) + return txid + + def fund_contract(self, contract): + txid = self.fund_htlc( + contract.currency, contract.p2sh, contract.amount) + return txid + + def fund_sell_contract(self, trade): + sell = trade.sell + txid = self.fund_htlc(sell.currency, sell.p2sh, sell.amount) + setattr(trade.sell, 'fund_tx', txid) + utils.save(trade) + return txid + + def create_sell_p2sh(self, trade, commitment, locktime): + # CREATE SELL CONTRACT + sell = trade.sell + contract = self.create_htlc(sell.currency, sell.initiator, + sell.fulfiller, commitment, locktime) + print("sell contract", contract) + setattr(trade.sell, 'p2sh', contract['p2sh']) + setattr(trade.sell, 'redeemScript', contract['redeemScript']) + setattr(trade.sell, 'redeemblocknum', contract['redeemblocknum']) + setattr(trade.buy, 'locktime', contract['locktime']) + utils.save(trade) + + def create_buy_p2sh(self, trade, commitment, locktime): + # CREATE BUY CONTRACT + buy = trade.buy + print("\nNow creating buy contract on the {0} blockchain where you " + "will wait for the buyer to send funds...".format(buy.currency)) + buy_contract = self.create_htlc( + buy.currency, buy.fulfiller, buy.initiator, commitment, locktime) + print("Buy contract", buy_contract) + + setattr(trade.buy, 'p2sh', buy_contract['p2sh']) + setattr(trade.buy, 'redeemScript', buy_contract['redeemScript']) + setattr(trade.buy, 'redeemblocknum', buy_contract['redeemblocknum']) + setattr(trade.buy, 'locktime', buy_contract['locktime']) + print("\nNow contact the buyer and tell them to send funds to this " + "p2sh: {0}\n".format(trade.buy.p2sh)) + + utils.save(trade) + + def redeem_p2sh(self, contract, secret): + currency = contract.currency + if currency == 'bitcoin': + res = self.bitcoinRPC.redeem_contract(contract, secret) + else: + res = self.zcashRPC.redeem_contract(contract, secret) + return res + + def parse_secret(self, chain, txid): + if chain == 'bitcoin': + secret = self.bitcoinRPC.parse_secret(txid) + else: + secret = self.zcashRPC.parse_secret(txid) + return secret + + # Main functions determining user flow from command line + def buyer_redeem(self, trade): + userInput.authorize_buyer_redeem(trade) + if trade.sell.get_status() == 'redeemed': + print("You already redeemed the funds and acquired " + "{0} {1}".format(trade.sell.amount, trade.sell.currency)) + exit() + else: + # Buyer redeems seller's funded tx + # p2sh = trade.sell.p2sh + # currency = trade.sell.currency + # Buy contract is where seller disclosed secret in redeeming + if trade.buy.currency == 'bitcoin': + secret = self.bitcoinRPC.parse_secret(trade.buy.redeem_tx) + else: + secret = self.zcashRPC.parse_secret(trade.buy.redeem_tx) + print("Found secret in seller's redeem tx", secret) + redeem_tx = self.redeem_p2sh(trade.sell, secret) + setattr(trade.sell, 'redeem_tx', redeem_tx) + utils.save(trade) exit() - else: - # Seller redeems buyer's funded tx (contract in p2sh) - txs = redeem_p2sh(trade.buy, secret) - print("You have redeemed {0} {1}!".format(buy.amount, buy.currency)) - return txs -def buyer_fulfill(trade): - buy = trade.buy - sell = trade.sell - buy_p2sh_balance = check_p2sh(buy.currency, buy.p2sh) - sell_p2sh_balance = check_p2sh(sell.currency, sell.p2sh) + def seller_redeem_p2sh(self, trade, secret): + buy = trade.buy + userInput.authorize_seller_redeem(buy) - if buy_p2sh_balance == 0: - userInput.authorize_buyer_fulfill(sell_p2sh_balance, sell.currency, buy_p2sh_balance, buy.currency) - print("Buy amt:", buy.amount) - txid = fund_buy_contract(trade) - print("Fund tx txid:", txid) - else: - print("It looks like you've already funded the contract to buy {1}, the amount in escrow in the p2sh is {0}.".format(buy_p2sh_balance, buy.currency)) - print("Please wait for the seller to remove your funds from escrow to complete the trade.") - print_trade('buyer') + if trade.sell.get_status() == 'redeemed': + print("You already redeemed the funds and acquired " + "{0} {1}".format(buy.amount, buy.currency)) + exit() + else: + # Seller redeems buyer's funded tx (contract in p2sh) + txs = self.redeem_p2sh(trade.buy, secret) + print("You have redeemed " + "{0} {1}!".format(buy.amount, buy.currency)) + return txs -def initialize_trade(tradeid, **kwargs): - trade = Trade() - conf = kwargs['conf'] - if conf == 'cli': - init_addrs = userInput.get_initiator_addresses() - fulfill_addrs = userInput.get_fulfiller_addresses() - amounts = userInput.get_trade_amounts() - print("AMOUNTS", amounts) - else: - init_addrs = ADDRS[conf]['initiator'] - fulfill_addrs = ADDRS[conf]['fulfiller'] - amounts = ADDRS[conf]['amounts'] + def buyer_fulfill(self, trade): + buy = trade.buy + sell = trade.sell + buy_p2sh_balance = self.check_p2sh(buy.currency, buy.p2sh) + sell_p2sh_balance = self.check_p2sh(sell.currency, sell.p2sh) - sell = amounts['sell'] - buy = amounts['buy'] - sell_currency = sell['currency'] - buy_currency = buy['currency'] - sell['initiator'] = init_addrs[sell_currency] - buy['initiator'] = init_addrs[buy_currency] - sell['fulfiller'] = fulfill_addrs[sell_currency] - buy['fulfiller'] = fulfill_addrs[buy_currency] + if buy_p2sh_balance == 0: + userInput.authorize_buyer_fulfill( + sell_p2sh_balance, + sell.currency, + buy_p2sh_balance, + buy.currency) + print("Buy amt:", buy.amount) + txid = fund_buy_contract(trade) + print("Fund tx txid:", txid) + else: + print("It looks like you've already funded the contract to buy " + "{1}, the amount in escrow in the p2sh is " + "{0}.".format(buy_p2sh_balance, buy.currency)) + print("Please wait for the seller to remove your funds from " + "escrow to complete the trade.") + print_trade('buyer') - # initializing contract classes with addresses, currencies, and amounts - trade.sell = Contract(sell) - trade.buy = Contract(buy) - print(trade.sell.__dict__) - print(trade.buy.__dict__) - return tradeid, trade + def initialize_trade(self, tradeid, **kwargs): + trade = Trade() + conf = kwargs['conf'] + if conf == 'cli': + init_addrs = userInput.get_initiator_addresses() + fulfill_addrs = userInput.get_fulfiller_addresses() + amounts = userInput.get_trade_amounts() + print("AMOUNTS", amounts) + else: + init_addrs = ADDRS[conf]['initiator'] + fulfill_addrs = ADDRS[conf]['fulfiller'] + amounts = ADDRS[conf]['amounts'] + sell = amounts['sell'] + buy = amounts['buy'] + sell_currency = sell['currency'] + buy_currency = buy['currency'] + sell['initiator'] = init_addrs[sell_currency] + buy['initiator'] = init_addrs[buy_currency] + sell['fulfiller'] = fulfill_addrs[sell_currency] + buy['fulfiller'] = fulfill_addrs[buy_currency] -def seller_init(tradeid, trade): - secret = generate_password() - db.save_secret(tradeid, secret) - print("\nGenerated a secret preimage to lock funds. This will only be stored locally: ", secret) + # initializing contract classes with addresses, currencies, and amounts + trade.sell = Contract(sell) + trade.buy = Contract(buy) + print(trade.sell.__dict__) + print(trade.buy.__dict__) + return tradeid, trade - hash_of_secret = sha256(secret) - # TODO: Implement locktimes and mock block passage of time - sell_locktime = 20 - buy_locktime = 10 # Must be more than first tx - print("Creating pay-to-script-hash for sell contract...") + def seller_init(self, tradeid, trade): + secret = utils.generate_password() + db.save_secret(tradeid, secret) + print("\nGenerated a secret preimage to lock funds. This will only " + "be stored locally: ", secret) - # create the p2sh addrs - create_sell_p2sh(trade, hash_of_secret, sell_locktime) - create_buy_p2sh(trade, hash_of_secret, buy_locktime) + hash_of_secret = utils.sha256(secret) + # TODO: Implement locktimes and mock block passage of time + sell_locktime = 20 + buy_locktime = 10 # Must be more than first tx + print("Creating pay-to-script-hash for sell contract...") - trade.commitment = b2x(hash_of_secret) - print("TRADE after seller init", trade.toJSON()) - return trade + # create the p2sh addrs + self.create_sell_p2sh(trade, hash_of_secret, sell_locktime) + self.create_buy_p2sh(trade, hash_of_secret, buy_locktime) + + trade.commitment = utils.b2x(hash_of_secret) + print("TRADE after seller init", trade.toJSON()) + return trade diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 7fb8367..d952d75 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,53 +1,62 @@ import unittest +import unittest.mock as mock import xcat.cli as cli -import xcat.db as db -from xcat.tests.utils import mktrade -from xcat.trades import Trade, Contract +from xcat.tests.utils import test_trade +from xcat.trades import Trade -class SimpleTestCase(unittest.TestCase): - def setUp(self): - self.trade = mktrade() +class TestCLI(unittest.TestCase): - def test_exporttrade(self): - self.__class__.hexstr = cli.exporttrade('test') - self.assertTrue(int(self.hexstr, 16)) + def test_save_state(self): + pass + + def test_checkSellStatus(self): + pass + + def test_buyer_check_status(self): + pass + + def test_seller_check_status(self): + pass + + def test_checkBuyStatus(self): + pass def test_importtrade(self): - trade = cli.importtrade('test', self.__class__.hexstr) + pass + def test_wormhole_importtrade(self): + pass + + def test_exporttrade(self): + pass -class CliTest(SimpleTestCase): def test_findtrade(self): - trade = cli.findtrade('test') + pass + + def test_find_role(self): + pass + + def test_checktrade(self): + pass def test_newtrade(self): - trade = cli.newtrade('new', conf='regtest') - self.assertTrue(isinstance(trade, Trade)) + pass + + def test_listtrades(self): + pass def test_fundsell(self): - trade = db.get('new') - status = cli.seller_check_status(trade) - print("Trade status: {0}\n".format(status)) - self.assertEqual(status, 'init') - fund_tx = cli.fund_sell_contract(trade) - print("Sent fund_tx", fund_tx) + pass - # def test_fundbuy(self): - # trade = db.get('new') - # status = cli.buyer_check_status(trade) - # self.assertEqual(status, 'sellerFunded') - # fund_tx = cli.fund_contract(trade.buy) - # - # def test_seller_redeem(self): - # trade = db.get('new') - # status = cli.seller_check_status(trade) - # self.assertEqual(status, 'buyerFunded') - # - # def test_buyer_redeem(self): - # trade = db.get('new') - # status = cli.buyer_check_status(trade) - # self.assertEqual(status, 'sellerFunded') + def test_fundbuy(self): + pass + + def test_seller_redeem(self): + pass + + def test_buyer_redeem(self): + pass if __name__ == '__main__': diff --git a/xcat/tests/utils.py b/xcat/tests/utils.py index 867b7a5..8a82b68 100644 --- a/xcat/tests/utils.py +++ b/xcat/tests/utils.py @@ -1,8 +1,20 @@ -import xcat.db as db - -test_trade = {"sell": {"amount": 3.5, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 1.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} - -def mktrade(): - db.create(test_trade, 'test') - trade = db.get('test') - return trade +test_trade = { + "sell": { + "amount": 3.5, + "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", + "redeemblocknum": 1066, + "currency": "bitcoin", + "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", + "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", + "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", + "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, + "buy": { + "amount": 1.2, + "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", + "redeemblocknum": 3391, + "currency": "zcash", + "locktime": 10, + "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", + "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", + "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, + "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} From 577a003076baf58ae11084b61a2edfb252e782ea Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 17:12:43 -0600 Subject: [PATCH 02/25] refactor cli.py to work with protocol as an object --- xcat/cli.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xcat/cli.py b/xcat/cli.py index aa10367..a1cdda4 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -14,6 +14,7 @@ def save_state(trade, tradeid): def checkSellStatus(tradeid): + protocol = Protocol() trade = db.get(tradeid) status = seller_check_status(trade) print("Trade status: {0}\n".format(status)) @@ -25,7 +26,8 @@ def checkSellStatus(tradeid): save_state(trade, tradeid) elif status == 'buyerFunded': secret = db.get_secret(tradeid) - print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) + print("Retrieved secret to redeem funds for " + "{0}: {1}".format(tradeid, secret)) txs = protocol.seller_redeem_p2sh(trade, secret) if 'redeem_tx' in txs: trade.buy.redeem_tx = txs['redeem_tx'] @@ -43,6 +45,7 @@ def checkSellStatus(tradeid): def buyer_check_status(trade): + protocol = Protocol() sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': @@ -60,6 +63,7 @@ def buyer_check_status(trade): def seller_check_status(trade): + protocol = Protocol() sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': @@ -77,6 +81,7 @@ def seller_check_status(trade): def checkBuyStatus(tradeid): + protocol = Protocol() trade = db.get(tradeid) status = buyer_check_status(trade) print("Trade status: {0}\n".format(status)) @@ -114,6 +119,7 @@ def checkBuyStatus(tradeid): # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): + protocol = Protocol() trade = x2s(hexstr) trade = db.instantiate(trade) protocol.import_addrs(trade) @@ -157,6 +163,7 @@ def findtrade(tradeid): def find_role(contract): + protocol = Protocol() # When regtest created both addrs on same machine, role is both. if protocol.is_myaddr(contract.initiator): if protocol.is_myaddr(contract.fulfiller): @@ -187,6 +194,7 @@ def checktrade(tradeid): def newtrade(tradeid, **kwargs): + protocol = Protocol() print("Creating new XCAT trade...") erase_trade() tradeid, trade = protocol.initialize_trade(tradeid, conf=kwargs['conf']) From 5198a2b765e8e475a5a9e9616d1a45fde987358e Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 17:43:13 -0600 Subject: [PATCH 03/25] lint cli.py --- xcat/cli.py | 125 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index a1cdda4..16248dd 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -1,15 +1,15 @@ import argparse import textwrap import subprocess +import os import xcat.db as db import xcat.userInput as userInput +import xcat.utils as utils from xcat.protocol import Protocol -from xcat.trades import * -from xcat.utils import * def save_state(trade, tradeid): - save(trade) + utils.save(trade) db.create(trade, tradeid) @@ -37,47 +37,54 @@ def checkSellStatus(tradeid): print("Refund tx: ", txs['refund_tx']) save_state(trade, tradeid) # Remove from db? Or just from temporary file storage - cleanup(tradeid) + utils.cleanup(tradeid) elif status == 'sellerFunded': - print("Buyer has not yet funded the contract where you offered to buy {0}, please wait for them to complete their part.".format(trade.buy.currency)) + print("Buyer has not yet funded the contract where you offered to " + "buy {0}, please wait for them to complete " + "their part.".format(trade.buy.currency)) elif status == 'sellerRedeemed': - print("You have already redeemed the p2sh on the second chain of this trade.") + print("You have already redeemed the p2sh on the second chain of " + "this trade.") def buyer_check_status(trade): protocol = Protocol() - sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) - buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) + sellState = protocol.check_fund_status( + trade.sell.currency, trade.sell.p2sh) + buyState = protocol.check_fund_status( + trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': - return 'sellerFunded' # step1 + return 'sellerFunded' # step1 # TODO: Find funding txid. How does buyer get seller redeemed tx? elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'): - return 'sellerRedeemed' # step3 + return 'sellerRedeemed' # step3 elif sellState == 'funded' and buyState == 'funded': - return 'buyerFunded' # step2 + return 'buyerFunded' # step2 elif sellState == 'empty' and buyState == 'empty': if hasattr(trade.sell, 'redeem_tx'): - return 'buyerRedeemed' # step4 + return 'buyerRedeemed' # step4 else: return 'init' def seller_check_status(trade): protocol = Protocol() - sellState = protocol.check_fund_status(trade.sell.currency, trade.sell.p2sh) - buyState = protocol.check_fund_status(trade.buy.currency, trade.buy.p2sh) + sellState = protocol.check_fund_status( + trade.sell.currency, trade.sell.p2sh) + buyState = protocol.check_fund_status( + trade.buy.currency, trade.buy.p2sh) if sellState == 'funded' and buyState == 'empty': - return 'sellerFunded' # step1 + return 'sellerFunded' # step1 elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'): - return 'sellerRedeemed' # step3 + return 'sellerRedeemed' # step3 # TODO: How does seller get buyer funded tx? elif sellState == 'funded' and buyState == 'funded': - return 'buyerFunded' # step2 + return 'buyerFunded' # step2 elif sellState == 'empty' and buyState == 'empty': if hasattr(trade.buy, 'redeem_tx'): - return 'buyerRedeemed' # step4 + return 'buyerRedeemed' # step4 else: - return 'init' # step0 + return 'init' # step0 def checkBuyStatus(tradeid): @@ -86,12 +93,14 @@ def checkBuyStatus(tradeid): status = buyer_check_status(trade) print("Trade status: {0}\n".format(status)) if status == 'init': - print("Trade has not yet started, waiting for seller to fund the sell p2sh.") + print("Trade has not yet started, waiting for seller to fund the " + "sell p2sh.") elif status == 'buyerRedeemed': print("This trade is complete, both sides redeemed.") elif status == 'sellerFunded': print("One active trade available, fulfilling buyer contract...") - input("Type 'enter' to allow this program to send funds on your behalf.") + input("Type 'enter' to allow this program to send funds on your " + "behalf.") print("Trade commitment", trade.commitment) # if verify_p2sh(trade): fund_tx = protocol.fund_contract(trade.buy) @@ -102,7 +111,7 @@ def checkBuyStatus(tradeid): secret = protocol.find_secret_from_fundtx(trade.buy.currency, trade.buy.p2sh, trade.buy.fund_tx) - if secret != None: + if secret is not None: print("Found secret on blockchain in seller's redeem tx: ", secret) txs = protocol.redeem_p2sh(trade.sell, secret) if 'redeem_tx' in txs: @@ -120,7 +129,7 @@ def checkBuyStatus(tradeid): # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): protocol = Protocol() - trade = x2s(hexstr) + trade = utils.x2s(hexstr) trade = db.instantiate(trade) protocol.import_addrs(trade) print(trade.toJSON()) @@ -130,7 +139,8 @@ def importtrade(tradeid, hexstr=''): def wormhole_importtrade(): res = subprocess.call('wormhole receive', shell=True) if res == 0: - tradeid = input("Enter filename of received trade data to import (printed on line above): ") + tradeid = input("Enter filename of received trade data to import " + "(printed on line above): ") with open(tradeid) as infile: hexstr = infile.readline().strip() importtrade(tradeid, hexstr) @@ -143,9 +153,9 @@ def wormhole_importtrade(): # Export a trade by its tradeid def exporttrade(tradeid, wormhole=False): trade = db.get(tradeid) - hexstr = s2x(trade.toJSON()) + hexstr = utils.s2x(trade.toJSON()) if wormhole: - tradefile = os.path.join(ROOT_DIR, '.tmp/{0}'.format(tradeid)) + tradefile = os.path.join(utils.ROOT_DIR, '.tmp/{0}'.format(tradeid)) print(tradefile) with open(tradefile, '+w') as outfile: outfile.write(hexstr) @@ -178,42 +188,45 @@ def checktrade(tradeid): print("In checktrade") trade = db.get(tradeid) if find_role(trade.sell) == 'test': - input("Is this a test? Both buyer and seller addresses are yours, press 'enter' to test.") + input("Is this a test? Both buyer and seller addresses are yours, " + "press 'enter' to test.") checkSellStatus(tradeid) checkBuyStatus(tradeid) checkSellStatus(tradeid) checkBuyStatus(tradeid) elif find_role(trade.sell) == 'initiator': print("You are the seller in this trade.") - role = 'seller' + # role = 'seller' checkSellStatus(tradeid) else: print("You are the buyer in this trade.") - role = 'buyer' + # role = 'buyer' checkBuyStatus(tradeid) def newtrade(tradeid, **kwargs): protocol = Protocol() print("Creating new XCAT trade...") - erase_trade() + utils.erase_trade() tradeid, trade = protocol.initialize_trade(tradeid, conf=kwargs['conf']) print("Trade", trade) trade = protocol.seller_init(tradeid, trade) - print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n") + print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent " + "to the buyer.\n") save_state(trade, tradeid) return trade def listtrades(): print("Trades") - trades = db.dump() - for trade in trades: + trade_list = db.dump() + for trade in trade_list: print("{0}: {1}".format(trade[0], trade[1])) def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, description=textwrap.dedent('''\ == Trades == newtrade "tradeid" - create a new trade @@ -223,12 +236,25 @@ def main(): findtrade "tradeid" - find a trade by the tradeid ''')) - parser.add_argument("command", action="store", help="list commands") - parser.add_argument("arguments", action="store", nargs="*", help="add arguments") - parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole") - parser.add_argument("-c", "--conf", action="store", help="Use default trade data in conf file.") - parser.add_argument("-n", "--network", action="store", help="Set network to regtest or mainnet. Defaults to testnet while in alpha.") - # parser.add_argument("--daemon", "-d", action="store_true", help="Run as daemon process") + + parser.add_argument( + "command", action="store", help="list commands") + parser.add_argument( + "arguments", action="store", nargs="*", help="add arguments") + parser.add_argument( + "-w", "--wormhole", action="store_true", + help="Transfer trade data through magic-wormhole") + parser.add_argument( + "-c", "--conf", action="store", + help="Use default trade data in conf file.") + parser.add_argument( + "-n", "--network", action="store", + help=("Set network to regtest or mainnet. " + "Defaults to testnet while in alpha.")) + # parser.add_argument( + # "--daemon", "-d", action="store_true", + # help="Run as daemon process") + args = parser.parse_args() command = args.command @@ -236,35 +262,40 @@ def main(): if args.wormhole: wormhole_importtrade() else: - if len(args.arguments) != 2: throw("Usage: importtrade [tradeid] [hexstring]") + if len(args.arguments) != 2: + utils.throw("Usage: importtrade [tradeid] [hexstring]") tradeid = args.arguments[0] hexstr = args.arguments[1] importtrade(tradeid, hexstr) elif command == 'exporttrade': - if len(args.arguments) < 1: throw("Usage: exporttrade [tradeid]") + if len(args.arguments) < 1: + utils.throw("Usage: exporttrade [tradeid]") tradeid = args.arguments[0] exporttrade(tradeid, args.wormhole) elif command == "findtrade": - if len(args.arguments) < 1: throw("Usage: findtrade [tradeid]") + if len(args.arguments) < 1: + utils.throw("Usage: findtrade [tradeid]") print("Finding trade") key = args.arguments[0] findtrade(key) elif command == 'checktrade': - if len(args.arguments) < 1: throw("Usage: checktrade [tradeid]") + if len(args.arguments) < 1: + utils.throw("Usage: checktrade [tradeid]") tradeid = args.arguments[0] checktrade(tradeid) elif command == 'listtrades': listtrades() # TODO: function to tell if tradeid already exists for newtrade elif command == 'newtrade': - if len(args.arguments) < 1: throw("Usage: newtrade [tradeid]") + if len(args.arguments) < 1: + utils.throw("Usage: newtrade [tradeid]") tradeid = args.arguments[0] - if args.conf == None: + if args.conf is None: newtrade(tradeid, network=args.network, conf='cli') else: newtrade(tradeid, network=args.network, conf=args.conf) elif command == "daemon": - #TODO: not implemented + # TODO: not implemented print("Run as daemon process") # Ad hoc testing of workflow starts here elif command == "step1": From 5fc8cf0d13fa5ca8e1c18a823b0859edfcad364b Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 18:00:55 -0600 Subject: [PATCH 04/25] lint utils.py --- xcat/utils.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/xcat/utils.py b/xcat/utils.py index 5a1315c..94e3c27 100644 --- a/xcat/utils.py +++ b/xcat/utils.py @@ -1,80 +1,102 @@ -import hashlib, json, random, binascii -import xcat.trades as trades +import hashlib +import json +import random +import binascii import os +import xcat.trades as trades ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + ############################################ ########### Data conversion utils ########## ############################################ + + def b(string): """Convert a string to bytes""" return str.encode(string) + def x(h): """Convert a hex string to bytes""" return binascii.unhexlify(h.encode('utf8')) + def b2x(b): """Convert bytes to a hex string""" return binascii.hexlify(b).decode('utf8') + def x2s(hexstring): """Convert hex to a utf-8 string""" return binascii.unhexlify(hexstring).decode('utf-8') + def s2x(string): """Convert a utf-8 string to hex""" return b2x(b(string)) + def hex2dict(hexstr): jsonstr = x2s(hexstr) print(hexstr['fund_tx']) print(jsonstr) return json.loads(jsonstr) + def jsonformat(trade): return { - 'sell': trade.sell.__dict__, - 'buy': trade.buyContract.__dict__ + 'sell': trade.sell.__dict__, + 'buy': trade.buyContract.__dict__ } + ############################################ ########### Preimage utils ################# ############################################ + def generate_password(): - s = "1234567890abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" + s = ("1234567890abcdefghijklmnopqrstuvwxyz" + "01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ") passlen = 32 - p = "".join(random.sample(s,passlen)) + p = "".join(random.sample(s, passlen)) return p + def sha256(secret): preimage = secret.encode('utf8') h = hashlib.sha256(preimage).digest() return h + ############################################ ######## Error handling for CLI ############ ############################################ + def throw(err): print(err) exit() + ############################################# ######### xcat.json temp file ############# ############################################# + tmp_dir = os.path.join(ROOT_DIR, '.tmp') if not os.path.exists(tmp_dir): os.makedirs(tmp_dir) xcatjson = os.path.join(tmp_dir, 'xcat.json') + def save_trade(trade): with open(xcatjson, 'w+') as outfile: json.dump(trade, outfile) + def get_trade(): with open(xcatjson) as data_file: xcatdb = json.load(data_file) @@ -83,6 +105,7 @@ def get_trade(): trade = trades.Trade(sell, buy, commitment=xcatdb['commitment']) return trade + def erase_trade(): try: with open(xcatjson, 'w') as outfile: @@ -90,15 +113,17 @@ def erase_trade(): except: pass + def save(trade): # print("Saving trade") trade = { - 'sell': trade.sell.__dict__, - 'buy': trade.buy.__dict__, - 'commitment': trade.commitment + 'sell': trade.sell.__dict__, + 'buy': trade.buy.__dict__, + 'commitment': trade.commitment } save_trade(trade) + # Remove tmp files when trade is complete def cleanup(tradeid): try: From 346f98ff336fdc17b1faf19006059642f6936aac Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 18:32:13 -0600 Subject: [PATCH 05/25] first test implemented --- xcat/tests/test_cli.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index d952d75..f36a983 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,8 +1,8 @@ import unittest import unittest.mock as mock import xcat.cli as cli -from xcat.tests.utils import test_trade -from xcat.trades import Trade +# from xcat.tests.utils import test_trade +# from xcat.trades import Trade class TestCLI(unittest.TestCase): @@ -34,7 +34,24 @@ class TestCLI(unittest.TestCase): def test_findtrade(self): pass - def test_find_role(self): + @mock.patch('xcat.cli.Protocol') + def test_find_role_test(self, mock_protocol): + mock_protocol().is_myaddr.return_value = True + + test_contract = mock.MagicMock() + test_contract.initiator = 'test initiator' + test_contract.fulfiller = 'test fulfiller' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'test') + + @mock.patch('xcat.cli.Protocol') + def test_find_role_initiator(self, mock_protocol): + pass + + @mock.patch('xcat.cli.Protocol') + def test_find_role_fulfiller(self, mock_protocol): pass def test_checktrade(self): From ba1119570b896bea620ed5f3a52844eb749f3b2c Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 18:43:08 -0600 Subject: [PATCH 06/25] cleanup, and more tests --- xcat/tests/test_cli.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index f36a983..92ae1d5 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -36,11 +36,11 @@ class TestCLI(unittest.TestCase): @mock.patch('xcat.cli.Protocol') def test_find_role_test(self, mock_protocol): - mock_protocol().is_myaddr.return_value = True + mock_protocol().is_myaddr = lambda k: k == 'me' test_contract = mock.MagicMock() - test_contract.initiator = 'test initiator' - test_contract.fulfiller = 'test fulfiller' + test_contract.initiator = 'me' + test_contract.fulfiller = 'me' res = cli.find_role(test_contract) @@ -48,11 +48,27 @@ class TestCLI(unittest.TestCase): @mock.patch('xcat.cli.Protocol') def test_find_role_initiator(self, mock_protocol): - pass + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'me' + test_contract.fulfiller = 'you' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'initiator') @mock.patch('xcat.cli.Protocol') def test_find_role_fulfiller(self, mock_protocol): - pass + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'you' + test_contract.fulfiller = 'me' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'fulfiller') def test_checktrade(self): pass From 5604b1b064a0f12b322d252dfbe77fccc27b41b0 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 18:56:48 -0600 Subject: [PATCH 07/25] error case for find_role --- xcat/cli.py | 5 ++++- xcat/tests/test_cli.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/xcat/cli.py b/xcat/cli.py index 16248dd..e04c20e 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -181,7 +181,10 @@ def find_role(contract): else: return 'initiator' else: - return 'fulfiller' + if protocol.is_myaddr(contract.fulfiller): + return 'fulfiller' + else: + raise ValueError('You are not a participant in this contract.') def checktrade(tradeid): diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 92ae1d5..8bf0ecd 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -70,6 +70,21 @@ class TestCLI(unittest.TestCase): self.assertEqual(res, 'fulfiller') + @mock.patch('xcat.cli.Protocol') + def test_find_role_error(self, mock_protocol): + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'you' + test_contract.fulfiller = 'you' + + with self.assertRaises(ValueError) as context: + cli.find_role(test_contract) + + self.assertTrue( + 'You are not a participant in this contract.' + in str(context.exception)) + def test_checktrade(self): pass From f8b75307021c5fe60435d7f797aef3769454e870 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Mon, 4 Sep 2017 20:17:55 -0600 Subject: [PATCH 08/25] lint db --- xcat/db.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/xcat/db.py b/xcat/db.py index 42fc2d6..cdaec10 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -1,10 +1,7 @@ import plyvel import json -# import binascii -# import sys -# import ast -from xcat.trades import * -from xcat.utils import * +import xcat.utils as utils +from xcat.trades import Trade, Contract db = plyvel.DB('/tmp/xcatDB', create_if_missing=True) preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) @@ -20,7 +17,7 @@ def create(trade, tradeid): trade = json.dumps(trade) else: trade = trade.toJSON() - db.put(b(tradeid), b(trade)) + db.put(utils.b(tradeid), utils.b(trade)) # Uses the funding txid as the key to save trade @@ -29,11 +26,11 @@ def createByFundtx(trade): # # Save trade by initiating txid jt = json.loads(trade) txid = jt['sell']['fund_tx'] - db.put(b(txid), b(trade)) + db.put(utils.b(txid), utils.b(trade)) def get(tradeid): - rawtrade = db.get(b(tradeid)) + rawtrade = db.get(utils.b(tradeid)) tradestr = str(rawtrade, 'utf-8') trade = instantiate(tradestr) return trade @@ -42,7 +39,10 @@ def get(tradeid): def instantiate(trade): if type(trade) == str: tradestr = json.loads(trade) - trade = Trade(buy=Contract(tradestr['buy']), sell=Contract(tradestr['sell']), commitment=tradestr['commitment']) + trade = Trade( + buy=Contract(tradestr['buy']), + sell=Contract(tradestr['sell']), + commitment=tradestr['commitment']) return trade ############################################# @@ -52,11 +52,11 @@ def instantiate(trade): # Stores secret locally in key/value store by tradeid def save_secret(tradeid, secret): - res = preimageDB.put(b(tradeid), b(secret)) + preimageDB.put(utils.b(tradeid), utils.b(secret)) def get_secret(tradeid): - secret = preimageDB.get(b(tradeid)) + secret = preimageDB.get(utils.b(tradeid)) secret = str(secret, 'utf-8') return secret @@ -69,7 +69,7 @@ def dump(): results = [] with db.iterator() as it: for k, v in it: - j = json.loads(x2s(b2x(v))) + j = json.loads(utils.x2s(utils.b2x(v))) results.append((str(k, 'utf-8'), j)) return results @@ -78,7 +78,7 @@ def print_entries(): it = db.iterator() with db.iterator() as it: for k, v in it: - j = json.loads(x2s(b2x(v))) + j = json.loads(utils.x2s(utils.b2x(v))) print("Key:", k) print('val: ', j) # print('sell: ', j['sell']) From de8ec2df5419909c30afd8aa80b317f1bf438930 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 22:16:42 -0600 Subject: [PATCH 09/25] linting, fix SelectParams import --- xcat/bitcoinRPC.py | 19 ++++++++++--------- xcat/cli.py | 27 ++++++++++++++++++++++----- xcat/protocol.py | 2 +- xcat/tests/test_db.py | 9 ++++++--- xcat/trades.py | 12 ++++++++---- xcat/userInput.py | 8 ++++++++ xcat/xcatconf.py | 8 ++++---- xcat/zcashRPC.py | 40 +++++++++++++++++++++++----------------- 8 files changed, 82 insertions(+), 43 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 338d8e3..c983303 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -1,13 +1,9 @@ #!/usr/bin/env python3 import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import bitcoin import bitcoin.rpc -from bitcoin import SelectParams +# from bitcoin import SelectParams from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction from bitcoin.base58 import decode from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE @@ -17,17 +13,22 @@ from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, from xcat.utils import * import logging +if sys.version_info.major < 3: + sys.stderr.write('Sorry, Python 3.x required by this example.\n') + sys.exit(1) + FEE = 0.001*COIN + class bitcoinProxy(): def __init__(self, network='regtest', timeout=900): if network is not 'testnet' and network is not 'mainnet': - network='regtest' + network = 'regtest' logging.debug("NETWORK in proxy: {0}".format(network)) self.network = network self.timeout = timeout - SelectParams(self.network) + bitcoin.SelectParams(self.network) self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout) def validateaddress(self, addr): @@ -41,9 +42,9 @@ class bitcoinProxy(): print("TXINFO", decoded['vin'][0]) if('txid' in decoded['vin'][0]): sendid = decoded['vin'][0]['txid'] - if (sendid == fundtx_input ): + if (sendid == fundtx_input): print("Found funding tx: ", sendid) - return parse_secret(lx(tx['txid'])) + return self.parse_secret(lx(tx['txid'])) print("Redeem transaction with secret not found") return diff --git a/xcat/cli.py b/xcat/cli.py index 553b5bb..55131b8 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -2,6 +2,7 @@ import argparse import textwrap import subprocess import os +import logging import xcat.db as db import xcat.userInput as userInput import xcat.utils as utils @@ -35,7 +36,7 @@ def checkSellStatus(tradeid): if 'refund_tx' in txs: trade.buy.redeem_tx = txs['refund_tx'] print("Buyer refund tx: ", txs['refund_tx']) - txs = refund_contract(trade.sell) # Refund to seller + txs = protocol.refund_contract(trade.sell) # Refund to seller print("Your refund txid: ", txs['refund_tx']) save_state(trade, tradeid) # Remove from db? Or just from temporary file storage @@ -266,18 +267,21 @@ def main(): args = parser.parse_args() - if args.debug: + if hasattr(args, 'debug'): numeric_level = getattr(logging, 'DEBUG', None) - logging.basicConfig(format='%(levelname)s: %(message)s', level=numeric_level) + logging.basicConfig(format='%(levelname)s: %(message)s', + level=numeric_level) else: - logging.basicConfig(format='%(levelname)s: %(message)s', level='INFO') + logging.basicConfig(format='%(levelname)s: %(message)s', + level='INFO') - if args.network: + if hasattr(args, 'network'): NETWORK = args.network else: NETWORK = 'testnet' command = args.command + if command == 'importtrade': if args.wormhole: wormhole_importtrade() @@ -287,25 +291,31 @@ def main(): tradeid = args.arguments[0] hexstr = args.arguments[1] importtrade(tradeid, hexstr) + elif command == 'exporttrade': if len(args.arguments) < 1: utils.throw("Usage: exporttrade [tradeid]") tradeid = args.arguments[0] exporttrade(tradeid, args.wormhole) + elif command == "findtrade": if len(args.arguments) < 1: utils.throw("Usage: findtrade [tradeid]") print("Finding trade") key = args.arguments[0] findtrade(key) + elif command == 'checktrade': if len(args.arguments) < 1: utils.throw("Usage: checktrade [tradeid]") tradeid = args.arguments[0] checktrade(tradeid) + elif command == 'listtrades': listtrades() + # TODO: function to tell if tradeid already exists for newtrade + elif command == 'newtrade': if len(args.arguments) < 1: utils.throw("Usage: newtrade [tradeid]") @@ -314,20 +324,27 @@ def main(): newtrade(tradeid, network=NETWORK, conf='cli') else: newtrade(tradeid, network=NETWORK, conf=args.conf) + elif command == "daemon": # TODO: not implemented print("Run as daemon process") + # Ad hoc testing of workflow starts here + elif command == "step1": tradeid = args.arguments[0] checkSellStatus(tradeid) + elif command == "step2": tradeid = args.arguments[0] checkBuyStatus(tradeid) + elif command == "step3": + protocol = Protocol() protocol.generate(31) tradeid = args.arguments[0] checkSellStatus(tradeid) + elif command == "step4": # generate(1) tradeid = args.arguments[0] diff --git a/xcat/protocol.py b/xcat/protocol.py index c24336f..f3cfffb 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -260,7 +260,7 @@ class Protocol(): print(trade.buy.__dict__) return tradeid, trade - def seller_init(self, tradeid, trade): + def seller_init(self, tradeid, trade, network): secret = utils.generate_password() db.save_secret(tradeid, secret) print("\nGenerated a secret preimage to lock funds. This will only " diff --git a/xcat/tests/test_db.py b/xcat/tests/test_db.py index 88d138b..ef7a873 100644 --- a/xcat/tests/test_db.py +++ b/xcat/tests/test_db.py @@ -1,8 +1,10 @@ -import xcat.database as db -import unittest, json +import unittest import xcat.trades as trades +import xcat.database as db + class DatabaseTest(unittest.TestCase): + def setUp(self): self.data = {"sell": {"amount": 0.1, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", "redeemblocknum": 1066, "currency": "bitcoin", "initiator": "myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp", "p2sh": "2MuYSQ1uQ4CJg5Y5QL2vMmVPHNJ2KT5aJ6f", "fulfiller": "mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z", "fund_tx": "5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d59511566a2fdb"}, "buy": {"amount": 0.2, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9143ea29256c9d2888ca23de42a8b8e69ca2ec235b167023f0db17576a914c5acca6ef39c843c7a9c3ad01b2da95fe2edf5ba6888ac", "redeemblocknum": 3391, "currency": "zcash", "locktime": 10, "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} self.sell = trades.Contract(self.data['sell']) @@ -14,8 +16,9 @@ class DatabaseTest(unittest.TestCase): db.create(trade, 'test') def test_get(self): - trade = db.get('test') + # trade = db.get('test') print("Trade") + if __name__ == '__main__': unittest.main() diff --git a/xcat/trades.py b/xcat/trades.py index 81a753e..40a2ab2 100644 --- a/xcat/trades.py +++ b/xcat/trades.py @@ -1,19 +1,23 @@ import json + class Trade(object): def __init__(self, sell=None, buy=None, commitment=None): - '''Create a new trade with a sell contract and buy contract across two chains''' + '''Create a new trade with buy and sell contracts across two chains''' self.sell = sell self.buy = buy self.commitment = commitment def toJSON(self): - return json.dumps(self, default=lambda o: o.__dict__, - sort_keys=True, indent=4) + return json.dumps( + self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + class Contract(object): def __init__(self, data): - allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', 'redeemblocknum', 'locktime') + allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', + 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', + 'redeemblocknum', 'locktime') for key in data: if key in allowed: setattr(self, key, data[key]) diff --git a/xcat/userInput.py b/xcat/userInput.py index bf67a88..3373d71 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -4,10 +4,12 @@ from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy from xcat.xcatconf import * + def enter_trade_id(): tradeid = input("Enter a unique identifier for this trade: ") return tradeid + def get_trade_amounts(): amounts = {} sell_currency = input("Which currency would you like to trade out of (bitcoin or zcash)? ") @@ -34,10 +36,12 @@ def get_trade_amounts(): amounts['buy'] = buy return amounts + def authorize_fund_sell(htlcTrade): print('To complete your sell, send {0} {1} to this p2sh: {2}'.format(htlcTrade.sell.amount, htlcTrade.sell.currency, htlcTrade.sell.p2sh)) response = input("Type 'enter' to allow this program to send funds on your behalf.") + def get_initiator_addresses(): bitcoinRPC = bitcoinProxy() zcashRPC = zcashProxy() @@ -50,6 +54,7 @@ def get_initiator_addresses(): addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} return addresses + def get_fulfiller_addresses(): btc_addr = input("Enter the bitcoin address of the party you want to trade with: ") if btc_addr == '': @@ -62,12 +67,15 @@ def get_fulfiller_addresses(): addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} return addresses + def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency, buy_p2sh_balance, buy_currency): input("The seller's p2sh is funded with {0} {1}, type 'enter' if this is the amount you want to buy in {1}.".format(sell_p2sh_balance, sell_currency)) input("You have not send funds to the contract to buy {1} (requested amount: {0}), type 'enter' to allow this program to send the agreed upon funds on your behalf.".format(buy_p2sh_balance, buy_currency)) + def authorize_seller_redeem(buy): input("Buyer funded the contract where you offered to buy {0}, type 'enter' to redeem {1} {0} from {2}.".format(buy.currency, buy.amount, buy.p2sh)) + def authorize_buyer_redeem(trade): input("Seller funded the contract where you paid them in {0} to buy {1}, type 'enter' to redeem {2} {1} from {3}.".format(trade.buy.currency, trade.sell.currency, trade.sell.amount, trade.sell.p2sh)) diff --git a/xcat/xcatconf.py b/xcat/xcatconf.py index 6c77cab..ffe0937 100644 --- a/xcat/xcatconf.py +++ b/xcat/xcatconf.py @@ -2,12 +2,12 @@ ADDRS = { 'regtest': { "initiator": { - "bitcoin": "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b", - "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" + "bitcoin": "moAccTjGt6nRCoLKhVLrDCAkqDt7fnsAgC", + "zcash": "tmJBCsE4ZBcgi2LykoUyei5PDT1cQPkFxpf" }, "fulfiller": { - "bitcoin": "moRt56gJQGDNK46Y6fYy2HbooKnQXrTGDN", - "zcash": "tmK3rGzHDqa78MCwEicx9VcY9ZWX9gCF2nd" + "bitcoin": "mxdJ47MeEeqrBDjHj7SrSLFoDuSP3G37t5", + "zcash": "tmBbe7hWtexP94638H1QUD9Z92BM4ZiXXgA" }, "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} }, diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index b2cafd0..d0d7ec4 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -1,29 +1,30 @@ #!/usr/bin/env python3 import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import zcash import zcash.rpc -from zcash import SelectParams +# from zcash import SelectParams from zcash.core import b2x, lx, x, b2lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160 from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress -import logging +# import logging from xcat.utils import x2s +if sys.version_info.major < 3: + sys.stderr.write('Sorry, Python 3.x required by this example.\n') + sys.exit(1) + FEE = 0.001*COIN + class zcashProxy(): def __init__(self, network='regtest', timeout=900): self.network = network self.timeout = timeout - SelectParams(self.network) + zcash.SelectParams(self.network) self.zcashd = zcash.rpc.Proxy(timeout=self.timeout) def validateaddress(self, addr): @@ -108,7 +109,7 @@ class zcashProxy(): # print("TXINFO", decoded['vin'][0]) if('txid' in decoded['vin'][0]): sendid = decoded['vin'][0]['txid'] - if (sendid == fundtx_input ): + if (sendid == fundtx_input): print("Found funding tx: ", sendid) return self.parse_secret(lx(tx['txid'])) print("Redeem transaction with secret not found") @@ -155,7 +156,7 @@ class zcashProxy(): def redeem_contract(self, contract, secret): # How to find redeemScript and redeemblocknum from blockchain? p2sh = contract.p2sh - #checking there are funds in the address + # checking there are funds in the address amount = self.check_funds(p2sh) if(amount == 0): print("Address ", p2sh, " not funded") @@ -184,7 +185,8 @@ class zcashProxy(): print('redeemPubKey', redeemPubKey) zec_redeemScript = CScript(x(contract.redeemScript)) txin = CMutableTxIn(fundtx['outpoint']) - txout = CMutableTxOut(fundtx['amount'] - FEE, redeemPubKey.to_scriptPubKey()) + txout = CMutableTxOut(fundtx['amount'] - FEE, + redeemPubKey.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) sighash = SignatureHash(zec_redeemScript, tx, 0, SIGHASH_ALL) @@ -193,15 +195,17 @@ class zcashProxy(): sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) print("SECRET", secret) preimage = secret.encode('utf-8') - txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, zec_redeemScript]) + txin.scriptSig = CScript([sig, privkey.pub, preimage, + OP_TRUE, zec_redeemScript]) txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey() print('Raw redeem transaction hex: ', b2x(tx.serialize())) - VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + VerifyScript(txin.scriptSig, txin_scriptPubKey, + tx, 0, (SCRIPT_VERIFY_P2SH,)) print("Script verified, sending raw redeem transaction...") txid = self.zcashd.sendrawtransaction(tx) - redeem_tx = b2x(lx(b2x(txid))) + redeem_tx = b2x(lx(b2x(txid))) fund_tx = str(fundtx['outpoint']) - return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} + return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} def refund(self, contract): fundtx = self.find_transaction_to_address(contract.p2sh) @@ -211,7 +215,8 @@ class zcashProxy(): redeemScript = CScript(x(contract.redeemScript)) txin = CMutableTxIn(fundtx['outpoint']) - txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) + txout = CMutableTxOut(fundtx['amount'] - FEE, + refundPubKey.to_scriptPubKey()) # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) @@ -222,10 +227,11 @@ class zcashProxy(): # txin.nSequence = 2185 txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize()))) - res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + res = VerifyScript(txin.scriptSig, txin_scriptPubKey, + tx, 0, (SCRIPT_VERIFY_P2SH,)) print("Script verified, sending raw transaction... (NOT)", res) txid = self.zcashd.sendrawtransaction(tx) - refund_tx = b2x(lx(b2x(txid))) + refund_tx = b2x(lx(b2x(txid))) fund_tx = str(fundtx['outpoint']) return {"refund_tx": refund_tx, "fund_tx": fund_tx} From 917acd1efc585f4c0150249e4903ea0a7fd6b067 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 22:52:21 -0600 Subject: [PATCH 10/25] undo xcatconf change, pull in old tests, create a older for unit tests --- xcat/cli.py | 12 +++- xcat/tests/test_cli.py | 130 +++++++++++------------------------- xcat/tests/unit/test_cli.py | 111 ++++++++++++++++++++++++++++++ xcat/tests/utils.py | 9 ++- xcat/xcatconf.py | 14 ++-- 5 files changed, 173 insertions(+), 103 deletions(-) create mode 100644 xcat/tests/unit/test_cli.py diff --git a/xcat/cli.py b/xcat/cli.py index 55131b8..ae32374 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -215,14 +215,20 @@ def newtrade(tradeid, **kwargs): protocol = Protocol() print("Creating new XCAT trade...") utils.erase_trade() + + conf = kwargs['conf'] if 'conf' in kwargs else 'regtest' + network = kwargs['network'] if 'network' in kwargs else 'regtest' + tradeid, trade = protocol.initialize_trade( tradeid, - conf=kwargs['conf'], - network=kwargs['network']) + conf=conf, + network=network) print("New trade created: {0}".format(trade)) - trade = protocol.seller_init(tradeid, trade, network=kwargs['network']) + + trade = protocol.seller_init(tradeid, trade, network=network) print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent " "to the buyer.\n") + save_state(trade, tradeid) return trade diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 8bf0ecd..1ca1ef9 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,111 +1,59 @@ import unittest -import unittest.mock as mock import xcat.cli as cli -# from xcat.tests.utils import test_trade -# from xcat.trades import Trade +import xcat.db as db +from xcat.protocol import Protocol +from xcat.tests.utils import mktrade +from xcat.trades import Trade # , Contract -class TestCLI(unittest.TestCase): - - def test_save_state(self): - pass - - def test_checkSellStatus(self): - pass - - def test_buyer_check_status(self): - pass - - def test_seller_check_status(self): - pass - - def test_checkBuyStatus(self): - pass - - def test_importtrade(self): - pass - - def test_wormhole_importtrade(self): - pass +class SimpleTestCase(unittest.TestCase): + def setUp(self): + self.trade = mktrade() def test_exporttrade(self): + self.__class__.hexstr = cli.exporttrade('test') + self.assertTrue(int(self.hexstr, 16)) + + def test_importtrade(self): + # trade = cli.importtrade('test', self.__class__.hexstr) pass + +class CliTest(SimpleTestCase): def test_findtrade(self): - pass - - @mock.patch('xcat.cli.Protocol') - def test_find_role_test(self, mock_protocol): - mock_protocol().is_myaddr = lambda k: k == 'me' - - test_contract = mock.MagicMock() - test_contract.initiator = 'me' - test_contract.fulfiller = 'me' - - res = cli.find_role(test_contract) - - self.assertEqual(res, 'test') - - @mock.patch('xcat.cli.Protocol') - def test_find_role_initiator(self, mock_protocol): - mock_protocol().is_myaddr = lambda k: k == 'me' - - test_contract = mock.MagicMock() - test_contract.initiator = 'me' - test_contract.fulfiller = 'you' - - res = cli.find_role(test_contract) - - self.assertEqual(res, 'initiator') - - @mock.patch('xcat.cli.Protocol') - def test_find_role_fulfiller(self, mock_protocol): - mock_protocol().is_myaddr = lambda k: k == 'me' - - test_contract = mock.MagicMock() - test_contract.initiator = 'you' - test_contract.fulfiller = 'me' - - res = cli.find_role(test_contract) - - self.assertEqual(res, 'fulfiller') - - @mock.patch('xcat.cli.Protocol') - def test_find_role_error(self, mock_protocol): - mock_protocol().is_myaddr = lambda k: k == 'me' - - test_contract = mock.MagicMock() - test_contract.initiator = 'you' - test_contract.fulfiller = 'you' - - with self.assertRaises(ValueError) as context: - cli.find_role(test_contract) - - self.assertTrue( - 'You are not a participant in this contract.' - in str(context.exception)) - - def test_checktrade(self): + # trade = cli.findtrade('test') pass def test_newtrade(self): - pass - - def test_listtrades(self): - pass + trade = cli.newtrade('new', conf='regtest') + self.assertTrue(isinstance(trade, Trade)) def test_fundsell(self): - pass + protocol = Protocol() - def test_fundbuy(self): - pass + trade = db.get('new') + status = cli.seller_check_status(trade) + print("Trade status: {0}\n".format(status)) + self.assertEqual(status, 'init') - def test_seller_redeem(self): - pass - - def test_buyer_redeem(self): - pass + fund_tx = protocol.fund_sell_contract(trade) + print("Sent fund_tx", fund_tx) + # def test_fundbuy(self): + # trade = db.get('new') + # status = cli.buyer_check_status(trade) + # self.assertEqual(status, 'sellerFunded') + # fund_tx = cli.fund_contract(trade.buy) + # + # def test_seller_redeem(self): + # trade = db.get('new') + # status = cli.seller_check_status(trade) + # self.assertEqual(status, 'buyerFunded') + # + # def test_buyer_redeem(self): + # trade = db.get('new') + # status = cli.buyer_check_status(trade) + # self.assertEqual(status, 'sellerFunded') if __name__ == '__main__': unittest.main() diff --git a/xcat/tests/unit/test_cli.py b/xcat/tests/unit/test_cli.py new file mode 100644 index 0000000..8bf0ecd --- /dev/null +++ b/xcat/tests/unit/test_cli.py @@ -0,0 +1,111 @@ +import unittest +import unittest.mock as mock +import xcat.cli as cli +# from xcat.tests.utils import test_trade +# from xcat.trades import Trade + + +class TestCLI(unittest.TestCase): + + def test_save_state(self): + pass + + def test_checkSellStatus(self): + pass + + def test_buyer_check_status(self): + pass + + def test_seller_check_status(self): + pass + + def test_checkBuyStatus(self): + pass + + def test_importtrade(self): + pass + + def test_wormhole_importtrade(self): + pass + + def test_exporttrade(self): + pass + + def test_findtrade(self): + pass + + @mock.patch('xcat.cli.Protocol') + def test_find_role_test(self, mock_protocol): + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'me' + test_contract.fulfiller = 'me' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'test') + + @mock.patch('xcat.cli.Protocol') + def test_find_role_initiator(self, mock_protocol): + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'me' + test_contract.fulfiller = 'you' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'initiator') + + @mock.patch('xcat.cli.Protocol') + def test_find_role_fulfiller(self, mock_protocol): + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'you' + test_contract.fulfiller = 'me' + + res = cli.find_role(test_contract) + + self.assertEqual(res, 'fulfiller') + + @mock.patch('xcat.cli.Protocol') + def test_find_role_error(self, mock_protocol): + mock_protocol().is_myaddr = lambda k: k == 'me' + + test_contract = mock.MagicMock() + test_contract.initiator = 'you' + test_contract.fulfiller = 'you' + + with self.assertRaises(ValueError) as context: + cli.find_role(test_contract) + + self.assertTrue( + 'You are not a participant in this contract.' + in str(context.exception)) + + def test_checktrade(self): + pass + + def test_newtrade(self): + pass + + def test_listtrades(self): + pass + + def test_fundsell(self): + pass + + def test_fundbuy(self): + pass + + def test_seller_redeem(self): + pass + + def test_buyer_redeem(self): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/xcat/tests/utils.py b/xcat/tests/utils.py index 8a82b68..ecb652b 100644 --- a/xcat/tests/utils.py +++ b/xcat/tests/utils.py @@ -1,3 +1,5 @@ +from xcat import db + test_trade = { "sell": { "amount": 3.5, @@ -17,4 +19,9 @@ test_trade = { "initiator": "tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ", "p2sh": "t2HP59RpfR34nBCWH4VVD497tkc2ikzgniP", "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, - "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} + "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} + +def mktrade(): + db.create(test_trade, 'test') + trade = db.get('test') + return trade diff --git a/xcat/xcatconf.py b/xcat/xcatconf.py index ffe0937..6efb053 100644 --- a/xcat/xcatconf.py +++ b/xcat/xcatconf.py @@ -2,12 +2,12 @@ ADDRS = { 'regtest': { "initiator": { - "bitcoin": "moAccTjGt6nRCoLKhVLrDCAkqDt7fnsAgC", - "zcash": "tmJBCsE4ZBcgi2LykoUyei5PDT1cQPkFxpf" + "bitcoin": "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b", + "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" }, "fulfiller": { - "bitcoin": "mxdJ47MeEeqrBDjHj7SrSLFoDuSP3G37t5", - "zcash": "tmBbe7hWtexP94638H1QUD9Z92BM4ZiXXgA" + "bitcoin": "moRt56gJQGDNK46Y6fYy2HbooKnQXrTGDN", + "zcash": "tmK3rGzHDqa78MCwEicx9VcY9ZWX9gCF2nd" }, "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} }, @@ -17,11 +17,9 @@ ADDRS = { "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" }, "fulfiller": { - "bitcoin": "mn2boR7rYq9DaAWWrVN5MazHKFyf7UhdyU", - "zcash": "tmErB22A1G74aq32aAh5AoqgQSJsAAAdT2p" + "bitcoin": "mm2smEJjRN4xoijEfpb5XvYd8e3EYWezom", + "zcash": "tmPwPdceaJAHQn7UiRCVnJ5tXBXHVqWMkis" }, "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} } } - -NETWORK = 'testnet' From 08b90c34948b06c4ed332ac7662779286f31b4d1 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 22:56:39 -0600 Subject: [PATCH 11/25] update import in test_db.py --- xcat/tests/test_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcat/tests/test_db.py b/xcat/tests/test_db.py index ef7a873..0595234 100644 --- a/xcat/tests/test_db.py +++ b/xcat/tests/test_db.py @@ -1,6 +1,6 @@ import unittest import xcat.trades as trades -import xcat.database as db +import xcat.db as db class DatabaseTest(unittest.TestCase): From 27f32ff57dbf7444f1938659ef3318bc9ac656a3 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 23:28:36 -0600 Subject: [PATCH 12/25] refactor db to be an object --- xcat/cli.py | 12 +++- xcat/db.py | 128 ++++++++++++++++++++--------------------- xcat/protocol.py | 5 +- xcat/tests/test_cli.py | 4 +- xcat/tests/test_db.py | 3 +- xcat/tests/utils.py | 4 +- 6 files changed, 84 insertions(+), 72 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index ae32374..989ac18 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -3,22 +3,26 @@ import textwrap import subprocess import os import logging -import xcat.db as db +from xcat.db import DB import xcat.userInput as userInput import xcat.utils as utils from xcat.protocol import Protocol def save_state(trade, tradeid): + db = DB() utils.save(trade) db.create(trade, tradeid) def checkSellStatus(tradeid): + db = DB() protocol = Protocol() + trade = db.get(tradeid) status = seller_check_status(trade) print("Trade status: {0}\n".format(status)) + if status == 'init': userInput.authorize_fund_sell(trade) fund_tx = protocol.fund_sell_contract(trade) @@ -91,6 +95,7 @@ def seller_check_status(trade): def checkBuyStatus(tradeid): + db = DB() protocol = Protocol() trade = db.get(tradeid) status = buyer_check_status(trade) @@ -132,6 +137,7 @@ def checkBuyStatus(tradeid): # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): + db = DB() protocol = Protocol() trade = utils.x2s(hexstr) trade = db.instantiate(trade) @@ -156,6 +162,7 @@ def wormhole_importtrade(): # Export a trade by its tradeid def exporttrade(tradeid, wormhole=False): + db = DB() trade = db.get(tradeid) hexstr = utils.s2x(trade.toJSON()) if wormhole: @@ -171,6 +178,7 @@ def exporttrade(tradeid, wormhole=False): def findtrade(tradeid): + db = DB() trade = db.get(tradeid) print(trade.toJSON()) return trade @@ -192,6 +200,7 @@ def find_role(contract): def checktrade(tradeid): + db = DB() print("In checktrade") trade = db.get(tradeid) if find_role(trade.sell) == 'test': @@ -234,6 +243,7 @@ def newtrade(tradeid, **kwargs): def listtrades(): + db = DB() print("Trades") trade_list = db.dump() for trade in trade_list: diff --git a/xcat/db.py b/xcat/db.py index cdaec10..d0a19b5 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -3,82 +3,78 @@ import json import xcat.utils as utils from xcat.trades import Trade, Contract -db = plyvel.DB('/tmp/xcatDB', create_if_missing=True) -preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) -############################################# -######## Trades stored by tradeid ########### -############################################# +class DB(): + def __init__(self): + self.db = plyvel.DB('/tmp/xcatDB', create_if_missing=True) + self.preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True) -# Takes dict or obj, saves json str as bytes -def create(trade, tradeid): - if type(trade) == dict: - trade = json.dumps(trade) - else: + ############################################# + ######## Trades stored by tradeid ########### + ############################################# + + # Takes dict or obj, saves json str as bytes + def create(self, trade, tradeid): + if type(trade) == dict: + trade = json.dumps(trade) + else: + trade = trade.toJSON() + self.db.put(utils.b(tradeid), utils.b(trade)) + + # Uses the funding txid as the key to save trade + def createByFundtx(self, trade): trade = trade.toJSON() - db.put(utils.b(tradeid), utils.b(trade)) + # # Save trade by initiating txid + jt = json.loads(trade) + txid = jt['sell']['fund_tx'] + self.db.put(utils.b(txid), utils.b(trade)) - -# Uses the funding txid as the key to save trade -def createByFundtx(trade): - trade = trade.toJSON() - # # Save trade by initiating txid - jt = json.loads(trade) - txid = jt['sell']['fund_tx'] - db.put(utils.b(txid), utils.b(trade)) - - -def get(tradeid): - rawtrade = db.get(utils.b(tradeid)) - tradestr = str(rawtrade, 'utf-8') - trade = instantiate(tradestr) - return trade - - -def instantiate(trade): - if type(trade) == str: - tradestr = json.loads(trade) - trade = Trade( - buy=Contract(tradestr['buy']), - sell=Contract(tradestr['sell']), - commitment=tradestr['commitment']) + def get(self, tradeid): + rawtrade = self.db.get(utils.b(tradeid)) + tradestr = str(rawtrade, 'utf-8') + trade = self.instantiate(tradestr) return trade -############################################# -###### Preimages stored by tradeid ########## -############################################# + def instantiate(self, trade): + if type(trade) == str: + tradestr = json.loads(trade) + trade = Trade( + buy=Contract(tradestr['buy']), + sell=Contract(tradestr['sell']), + commitment=tradestr['commitment']) + return trade + ############################################# + ###### Preimages stored by tradeid ########## + ############################################# -# Stores secret locally in key/value store by tradeid -def save_secret(tradeid, secret): - preimageDB.put(utils.b(tradeid), utils.b(secret)) + # Stores secret locally in key/value store by tradeid + def save_secret(self, tradeid, secret): + self.preimageDB.put(utils.b(tradeid), utils.b(secret)) + def get_secret(self, tradeid): + secret = self.preimageDB.get(utils.b(tradeid)) + secret = str(secret, 'utf-8') + return secret -def get_secret(tradeid): - secret = preimageDB.get(utils.b(tradeid)) - secret = str(secret, 'utf-8') - return secret + ############################################# + ########## Dump or view db entries ########## + ############################################# + def dump(self): + results = [] + with self.db.iterator() as it: + for k, v in it: + j = json.loads(utils.x2s(utils.b2x(v))) + results.append((str(k, 'utf-8'), j)) + return results -############################################# -########## Dump or view db entries ########## -############################################# - -def dump(): - results = [] - with db.iterator() as it: - for k, v in it: - j = json.loads(utils.x2s(utils.b2x(v))) - results.append((str(k, 'utf-8'), j)) - return results - - -def print_entries(): - it = db.iterator() - with db.iterator() as it: - for k, v in it: - j = json.loads(utils.x2s(utils.b2x(v))) - print("Key:", k) - print('val: ', j) - # print('sell: ', j['sell']) + def print_entries(self): + it = self.db.iterator() + with self.db.iterator() as it: + for k, v in it: + j = json.loads(utils.x2s(utils.b2x(v))) + print("Key:", k) + print('val: ', j) + # print('sell: ', j['sell']) diff --git a/xcat/protocol.py b/xcat/protocol.py index f3cfffb..db6cb62 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -1,11 +1,11 @@ +import logging import xcat.userInput as userInput -import xcat.db as db import xcat.utils as utils from xcat.xcatconf import ADDRS from xcat.trades import Contract, Trade from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy -import logging +from xcat.db import DB class Protocol(): @@ -261,6 +261,7 @@ class Protocol(): return tradeid, trade def seller_init(self, tradeid, trade, network): + db = DB() secret = utils.generate_password() db.save_secret(tradeid, secret) print("\nGenerated a secret preimage to lock funds. This will only " diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 1ca1ef9..5d012cf 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,6 +1,6 @@ import unittest import xcat.cli as cli -import xcat.db as db +from xcat.db import DB from xcat.protocol import Protocol from xcat.tests.utils import mktrade from xcat.trades import Trade # , Contract @@ -29,6 +29,7 @@ class CliTest(SimpleTestCase): self.assertTrue(isinstance(trade, Trade)) def test_fundsell(self): + db = DB() protocol = Protocol() trade = db.get('new') @@ -55,5 +56,6 @@ class CliTest(SimpleTestCase): # status = cli.buyer_check_status(trade) # self.assertEqual(status, 'sellerFunded') + if __name__ == '__main__': unittest.main() diff --git a/xcat/tests/test_db.py b/xcat/tests/test_db.py index 0595234..74e1ca0 100644 --- a/xcat/tests/test_db.py +++ b/xcat/tests/test_db.py @@ -1,6 +1,6 @@ import unittest import xcat.trades as trades -import xcat.db as db +from xcat.db import DB class DatabaseTest(unittest.TestCase): @@ -10,6 +10,7 @@ class DatabaseTest(unittest.TestCase): self.sell = trades.Contract(self.data['sell']) def test_create(self): + db = DB() sell = trades.Contract(self.data['sell']) buy = trades.Contract(self.data['buy']) trade = trades.Trade(sell, buy, commitment=self.data['commitment']) diff --git a/xcat/tests/utils.py b/xcat/tests/utils.py index ecb652b..9f556e9 100644 --- a/xcat/tests/utils.py +++ b/xcat/tests/utils.py @@ -1,4 +1,4 @@ -from xcat import db +from xcat.db import DB test_trade = { "sell": { @@ -21,7 +21,9 @@ test_trade = { "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} + def mktrade(): + db = DB() db.create(test_trade, 'test') trade = db.get('test') return trade From 2745b466926744248b15092704a22bbe0930d21f Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 23:32:39 -0600 Subject: [PATCH 13/25] make db.instantiate static --- xcat/db.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xcat/db.py b/xcat/db.py index d0a19b5..e1ea1cf 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -36,7 +36,8 @@ class DB(): trade = self.instantiate(tradestr) return trade - def instantiate(self, trade): + @staticmethod + def instantiate(trade): if type(trade) == str: tradestr = json.loads(trade) trade = Trade( From cdda932dde0806062763d3ed4e860aef3484183c Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 23:44:25 -0600 Subject: [PATCH 14/25] lint bitcoinRPC --- xcat/bitcoinRPC.py | 79 ++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index c983303..0468815 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -4,13 +4,17 @@ import sys import bitcoin import bitcoin.rpc # from bitcoin import SelectParams -from bitcoin.core import b2x, lx, b2lx, x, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160, CTransaction -from bitcoin.base58 import decode -from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE, OP_FALSE +from bitcoin.core import b2x, lx, x, COIN, CMutableTxOut +from bitcoin.core import CMutableTxIn, CMutableTransaction +from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF +from bitcoin.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG +from bitcoin.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP +from bitcoin.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH -from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress +from bitcoin.wallet import CBitcoinAddress, P2SHBitcoinAddress +from bitcoin.wallet import P2PKHBitcoinAddress -from xcat.utils import * +import xcat.utils as utils import logging if sys.version_info.major < 3: @@ -53,9 +57,9 @@ class bitcoinProxy(): decoded = zcashd.call('decoderawtransaction', raw) scriptSig = decoded['vin'][0]['scriptSig'] asm = scriptSig['asm'].split(" ") - pubkey = asm[1] - secret = x2s(asm[2]) - redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + # pubkey = asm[1] + secret = utils.x2s(asm[2]) + # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) return secret def get_keys(self, funder_address, redeemer_address): @@ -78,18 +82,27 @@ class bitcoinProxy(): print("Current blocknum on Bitcoin: ", blocknum) redeemblocknum = blocknum + locktime print("Redeemblocknum on Bitcoin: ", redeemblocknum) - redeemScript = CScript([OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, - redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, - funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) - # print("Redeem script for p2sh contract on Bitcoin blockchain: {0}".format(b2x(redeemScript))) + redeemScript = CScript( + [OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY, OP_DUP, OP_HASH160, + redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, + OP_DROP, OP_DUP, OP_HASH160, funderAddr, OP_ENDIF, OP_EQUALVERIFY, + OP_CHECKSIG]) + # print("Redeem script for p2sh contract on Bitcoin blockchain: " + # "{0}".format(b2x(redeemScript))) txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() # Convert the P2SH scriptPubKey to a base58 Bitcoin address - txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) + txin_p2sh_address = CBitcoinAddress.from_scriptPubKey( + txin_scriptPubKey) p2sh = str(txin_p2sh_address) # Import address at same time you create self.bitcoind.importaddress(p2sh, "", False) print("p2sh computed", p2sh) - return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} + return {'p2sh': p2sh, + 'redeemblocknum': redeemblocknum, + 'redeemScript': b2x(redeemScript), + 'redeemer': redeemer, + 'funder': funder, + 'locktime': locktime} def fund_htlc(self, p2sh, amount): send_amount = float(amount) * COIN @@ -117,7 +130,7 @@ class bitcoinProxy(): else: return 'empty' - ## TODO: FIX search for p2sh in block + # TODO: FIX search for p2sh in block def search_p2sh(self, block, p2sh): print("Fetching block...") blockdata = self.bitcoind.getblock(lx(block)) @@ -136,7 +149,8 @@ class bitcoinProxy(): for addr in vout['scriptPubKey']['addresses']: print("Sent to address:", addr) if addr == p2sh: - print("Address to p2sh found in transaction!", addr) + print("Address to p2sh found in transaction! ", + addr) print("Returning from search_p2sh") def get_tx_details(self, txid): @@ -149,9 +163,9 @@ class bitcoinProxy(): scriptarray = self.parse_script(contract.redeemScript) redeemblocknum = scriptarray[8] self.redeemPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[6])) - refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) + # refundPubKey = P2PKHBitcoinAddress.from_bytes(x(scriptarray[13])) p2sh = contract.p2sh - #checking there are funds in the address + # checking there are funds in the address amount = self.check_funds(p2sh) if(amount == 0): print("address ", p2sh, " not funded") @@ -178,7 +192,9 @@ class bitcoinProxy(): # TODO: Compare with script on blockchain? redeemScript = CScript(x(contract.redeemScript)) txin = CMutableTxIn(fundtx['outpoint']) - txout = CMutableTxOut(fundtx['amount'] - FEE, self.redeemPubKey.to_scriptPubKey()) + txout = CMutableTxOut(fundtx['amount'] - FEE, + self.redeemPubKey.to_scriptPubKey()) + # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) @@ -186,17 +202,19 @@ class bitcoinProxy(): privkey = self.bitcoind.dumpprivkey(self.redeemPubKey) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) preimage = secret.encode('utf-8') - txin.scriptSig = CScript([sig, privkey.pub, preimage, OP_TRUE, redeemScript]) + txin.scriptSig = CScript([sig, privkey.pub, preimage, + OP_TRUE, redeemScript]) # print("txin.scriptSig", b2x(txin.scriptSig)) txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() print('Raw redeem transaction hex: ', b2x(tx.serialize())) - VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + VerifyScript(txin.scriptSig, txin_scriptPubKey, + tx, 0, (SCRIPT_VERIFY_P2SH,)) print("Script verified, sending raw transaction...") txid = self.bitcoind.sendrawtransaction(tx) fund_tx = str(fundtx['outpoint']) - redeem_tx = b2x(lx(b2x(txid))) - return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} + redeem_tx = b2x(lx(b2x(txid))) + return {"redeem_tx": redeem_tx, "fund_tx": fund_tx} def refund(self, contract): fundtx = self.find_transaction_to_address(contract.p2sh) @@ -206,21 +224,26 @@ class bitcoinProxy(): redeemScript = CScript(x(contract.redeemScript)) txin = CMutableTxIn(fundtx['outpoint']) - txout = CMutableTxOut(fundtx['amount'] - FEE, refundPubKey.to_scriptPubKey()) + txout = CMutableTxOut(fundtx['amount'] - FEE, + refundPubKey.to_scriptPubKey()) + # Create the unsigned raw transaction. tx = CMutableTransaction([txin], [txout]) sighash = SignatureHash(redeemScript, tx, 0, SIGHASH_ALL) privkey = self.bitcoind.dumpprivkey(refundPubKey) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) + # Sign without secret txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript]) + # txin.nSequence = 2185 txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() print('Raw redeem transaction hex: {0}'.format(b2x(tx.serialize()))) - res = VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH,)) + res = VerifyScript(txin.scriptSig, txin_scriptPubKey, + tx, 0, (SCRIPT_VERIFY_P2SH,)) print("Script verified, sending raw transaction... (NOT)", res) txid = self.bitcoind.sendrawtransaction(tx) - refund_tx = b2x(lx(b2x(txid))) + refund_tx = b2x(lx(b2x(txid))) fund_tx = str(fundtx['outpoint']) return {"refund_tx": refund_tx, "fund_tx": fund_tx} @@ -230,12 +253,12 @@ class bitcoinProxy(): return scriptarray def find_redeemblocknum(self, contract): - scriptarray = parse_script(contract.redeemScript) + scriptarray = self.parse_script(contract.redeemScript) redeemblocknum = scriptarray[8] return int(redeemblocknum) def find_redeemAddr(self, contract): - scriptarray = parse_script(contract.redeemScript) + scriptarray = self.parse_script(contract.redeemScript) redeemer = scriptarray[6] redeemAddr = P2PKHBitcoinAddress.from_bytes(x(redeemer)) return redeemAddr From ec71e34b7a4d9160183530cafa03e0d624e16848 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 12 Sep 2017 23:48:56 -0600 Subject: [PATCH 15/25] lint zcashRPC --- xcat/bitcoinRPC.py | 10 +++++----- xcat/zcashRPC.py | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 0468815..b54b500 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -82,11 +82,11 @@ class bitcoinProxy(): print("Current blocknum on Bitcoin: ", blocknum) redeemblocknum = blocknum + locktime print("Redeemblocknum on Bitcoin: ", redeemblocknum) - redeemScript = CScript( - [OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY, OP_DUP, OP_HASH160, - redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, - OP_DROP, OP_DUP, OP_HASH160, funderAddr, OP_ENDIF, OP_EQUALVERIFY, - OP_CHECKSIG]) + redeemScript = CScript([ + OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY, OP_DUP, OP_HASH160, + redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, + OP_DROP, OP_DUP, OP_HASH160, funderAddr, OP_ENDIF, OP_EQUALVERIFY, + OP_CHECKSIG]) # print("Redeem script for p2sh contract on Bitcoin blockchain: " # "{0}".format(b2x(redeemScript))) txin_scriptPubKey = redeemScript.to_p2sh_scriptPubKey() diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index d0d7ec4..6892d61 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -3,12 +3,17 @@ import sys import zcash import zcash.rpc -# from zcash import SelectParams -from zcash.core import b2x, lx, x, b2lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, Hash160 -from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP, OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE -from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH -from zcash.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress # import logging +# from zcash import SelectParams +from zcash.core import b2x, lx, x, COIN +from zcash.core import CMutableTransaction, CMutableTxOut, CMutableTxIn +from zcash.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF +from zcash.core.script import OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG +from zcash.core.script import SignatureHash, SIGHASH_ALL, OP_FALSE, OP_DROP +from zcash.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE +from zcash.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH +from zcash.wallet import CBitcoinAddress, P2PKHBitcoinAddress +from zcash.wallet import P2SHBitcoinAddress from xcat.utils import x2s @@ -48,20 +53,31 @@ class zcashProxy(): print("Current blocknum on Zcash: ", blocknum) redeemblocknum = blocknum + locktime print("Redeemblocknum on Zcash: ", redeemblocknum) - # can rm op_dup and op_hash160 if you replace addrs with pubkeys (as raw hex/bin data?), and can rm last op_equalverify (for direct pubkey comparison) - zec_redeemScript = CScript([OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY,OP_DUP, OP_HASH160, - redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, - funderAddr, OP_ENDIF,OP_EQUALVERIFY, OP_CHECKSIG]) - # print("Redeem script for p2sh contract on Zcash blockchain: ", b2x(zec_redeemScript)) + # can rm op_dup and op_hash160 if you replace addrs with pubkeys + # (as raw hex/bin data?) + # can rm last op_equalverify (for direct pubkey comparison) + zec_redeemScript = CScript([ + OP_IF, OP_SHA256, commitment, OP_EQUALVERIFY, OP_DUP, OP_HASH160, + redeemerAddr, OP_ELSE, redeemblocknum, OP_CHECKLOCKTIMEVERIFY, + OP_DROP, OP_DUP, OP_HASH160, funderAddr, OP_ENDIF, OP_EQUALVERIFY, + OP_CHECKSIG]) + # print("Redeem script for p2sh contract on Zcash blockchain: ", + # b2x(zec_redeemScript)) txin_scriptPubKey = zec_redeemScript.to_p2sh_scriptPubKey() # Convert the P2SH scriptPubKey to a base58 Bitcoin address - txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) + txin_p2sh_address = CBitcoinAddress.from_scriptPubKey( + txin_scriptPubKey) p2sh = str(txin_p2sh_address) print("p2sh computed: ", p2sh) # Import address as soon as you create it self.zcashd.importaddress(p2sh, "", False) # Returning all this to be saved locally in p2sh.json - return {'p2sh': p2sh, 'redeemblocknum': redeemblocknum, 'redeemScript': b2x(zec_redeemScript), 'redeemer': redeemer, 'funder': funder, 'locktime': locktime} + return {'p2sh': p2sh, + 'redeemblocknum': redeemblocknum, + 'redeemScript': b2x(zec_redeemScript), + 'redeemer': redeemer, + 'funder': funder, + 'locktime': locktime} def fund_htlc(self, p2sh, amount): send_amount = float(amount)*COIN From 4cf2a6b393092e9e89edad75d5f416086b51c4e6 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Wed, 13 Sep 2017 00:00:38 -0600 Subject: [PATCH 16/25] lint userInput --- xcat/userInput.py | 63 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/xcat/userInput.py b/xcat/userInput.py index 3373d71..d30dceb 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -1,8 +1,8 @@ -from xcat.utils import * -from xcat.db import * +# from xcat.utils import * +# from xcat.db import * from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy -from xcat.xcatconf import * +# from xcat.xcatconf import * def enter_trade_id(): @@ -12,8 +12,9 @@ def enter_trade_id(): def get_trade_amounts(): amounts = {} - sell_currency = input("Which currency would you like to trade out of (bitcoin or zcash)? ") - if sell_currency == '' or sell_currency == 'bitcoin' : + sell_currency = input("Which currency would you like to trade out of " + "(bitcoin or zcash)? ") + if sell_currency == '' or sell_currency == 'bitcoin': sell_currency = 'bitcoin' buy_currency = 'zcash' elif sell_currency == 'zcash': @@ -22,11 +23,13 @@ def get_trade_amounts(): else: raise ValueError('Mistyped or unspported cryptocurrency pair') print(sell_currency) - sell_amt = input("How much {0} do you want to sell? ".format(sell_currency)) + sell_amt = input("How much {0} do you " + "want to sell? ".format(sell_currency)) if sell_amt == '': sell_amt = 0.01 print(sell_amt) - buy_amt = input("How much {0} do you want to receive in exchange? ".format(buy_currency)) + buy_amt = input("How much {0} do you " + "want to receive in exchange? ".format(buy_currency)) if buy_amt == '': buy_amt = 0.02 print(buy_amt) @@ -38,17 +41,22 @@ def get_trade_amounts(): def authorize_fund_sell(htlcTrade): - print('To complete your sell, send {0} {1} to this p2sh: {2}'.format(htlcTrade.sell.amount, htlcTrade.sell.currency, htlcTrade.sell.p2sh)) - response = input("Type 'enter' to allow this program to send funds on your behalf.") + print('To complete your sell, send {0} {1} to this p2sh: ' + '{2}'.format(htlcTrade.sell.amount, + htlcTrade.sell.currency, + htlcTrade.sell.p2sh)) + input("Type 'enter' to allow this program to send funds on your behalf.") def get_initiator_addresses(): bitcoinRPC = bitcoinProxy() zcashRPC = zcashProxy() - btc_addr = input("Enter your bitcoin address or press enter to generate one: ") + btc_addr = input("Enter your bitcoin address " + "or press enter to generate one: ") btc_addr = bitcoinRPC.new_bitcoin_addr() print(btc_addr) - zec_addr = input("Enter your zcash address or press enter to generate one: ") + zec_addr = input("Enter your zcash address " + "or press enter to generate one: ") zec_addr = zcashRPC.new_zcash_addr() print(zec_addr) addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} @@ -56,26 +64,41 @@ def get_initiator_addresses(): def get_fulfiller_addresses(): - btc_addr = input("Enter the bitcoin address of the party you want to trade with: ") + btc_addr = input("Enter the bitcoin address of " + "the party you want to trade with: ") if btc_addr == '': - btc_addr = "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b" # regtest + btc_addr = "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b" # regtest print(btc_addr) - zec_addr = input("Enter the zcash address of the party you want to trade with: ") + + zec_addr = input("Enter the zcash address of " + "the party you want to trade with: ") if zec_addr == '': - zec_addr = "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" # regtest + zec_addr = "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc" # regtest print(zec_addr) + addresses = {'bitcoin': btc_addr, 'zcash': zec_addr} return addresses -def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency, buy_p2sh_balance, buy_currency): - input("The seller's p2sh is funded with {0} {1}, type 'enter' if this is the amount you want to buy in {1}.".format(sell_p2sh_balance, sell_currency)) - input("You have not send funds to the contract to buy {1} (requested amount: {0}), type 'enter' to allow this program to send the agreed upon funds on your behalf.".format(buy_p2sh_balance, buy_currency)) +def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency, + buy_p2sh_balance, buy_currency): + input("The seller's p2sh is funded with {0} {1}, " + "type 'enter' if this is the amount you want to buy " + "in {1}.".format(sell_p2sh_balance, sell_currency)) + input("You have not send funds to the contract to buy {1} " + "(requested amount: {0}), type 'enter' to allow this program " + "to send the agreed upon funds on your behalf.".format( + buy_p2sh_balance, buy_currency)) def authorize_seller_redeem(buy): - input("Buyer funded the contract where you offered to buy {0}, type 'enter' to redeem {1} {0} from {2}.".format(buy.currency, buy.amount, buy.p2sh)) + input("Buyer funded the contract where you offered to buy {0}, " + "type 'enter' to redeem {1} {0} from " + "{2}.".format(buy.currency, buy.amount, buy.p2sh)) def authorize_buyer_redeem(trade): - input("Seller funded the contract where you paid them in {0} to buy {1}, type 'enter' to redeem {2} {1} from {3}.".format(trade.buy.currency, trade.sell.currency, trade.sell.amount, trade.sell.p2sh)) + input("Seller funded the contract where you paid them in {0} " + "to buy {1}, type 'enter' to redeem {2} {1} from " + "{3}.".format(trade.buy.currency, trade.sell.currency, + trade.sell.amount, trade.sell.p2sh)) From f65641a2c7da2a0f2b00d23fc98f96d84022a773 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Wed, 13 Sep 2017 09:35:31 -0600 Subject: [PATCH 17/25] minor linting --- xcat/bitcoinRPC.py | 6 +++--- xcat/userInput.py | 4 ++-- xcat/zcashRPC.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index b54b500..e1fb00e 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -21,7 +21,7 @@ if sys.version_info.major < 3: sys.stderr.write('Sorry, Python 3.x required by this example.\n') sys.exit(1) -FEE = 0.001*COIN +FEE = 0.001 * COIN class bitcoinProxy(): @@ -117,13 +117,13 @@ class bitcoinProxy(): self.bitcoind.importaddress(p2sh, "", False) # Get amount in address amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN + amount = amount / COIN return amount def get_fund_status(self, p2sh): self.bitcoind.importaddress(p2sh, "", False) amount = self.bitcoind.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN + amount = amount / COIN print("Amount in bitcoin p2sh: ", amount, p2sh) if amount > 0: return 'funded' diff --git a/xcat/userInput.py b/xcat/userInput.py index d30dceb..03a83a5 100644 --- a/xcat/userInput.py +++ b/xcat/userInput.py @@ -87,8 +87,8 @@ def authorize_buyer_fulfill(sell_p2sh_balance, sell_currency, "in {1}.".format(sell_p2sh_balance, sell_currency)) input("You have not send funds to the contract to buy {1} " "(requested amount: {0}), type 'enter' to allow this program " - "to send the agreed upon funds on your behalf.".format( - buy_p2sh_balance, buy_currency)) + "to send the agreed upon funds on your behalf" + ".".format(buy_p2sh_balance, buy_currency)) def authorize_seller_redeem(buy): diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 6892d61..6a49b96 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -21,7 +21,7 @@ if sys.version_info.major < 3: sys.stderr.write('Sorry, Python 3.x required by this example.\n') sys.exit(1) -FEE = 0.001*COIN +FEE = 0.001 * COIN class zcashProxy(): @@ -80,7 +80,7 @@ class zcashProxy(): 'locktime': locktime} def fund_htlc(self, p2sh, amount): - send_amount = float(amount)*COIN + send_amount = float(amount) * COIN # Import addr at same time as you fund self.zcashd.importaddress(p2sh, "", False) fund_txid = self.zcashd.sendtoaddress(p2sh, send_amount) @@ -92,13 +92,13 @@ class zcashProxy(): self.zcashd.importaddress(p2sh, "", False) # Get amount in address amount = self.zcashd.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN + amount = amount / COIN return amount def get_fund_status(self, p2sh): self.zcashd.importaddress(p2sh, "", False) amount = self.zcashd.getreceivedbyaddress(p2sh, 0) - amount = amount/COIN + amount = amount / COIN print("Amount in zcash p2sh: ", amount, p2sh) if amount > 0: return 'funded' From 95a709e8196b4f0d356435c3c6c81d7daf04d460 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Thu, 14 Sep 2017 14:37:26 -0600 Subject: [PATCH 18/25] set up testrunner --- .gitignore | 14 ++++++++-- requirements-test.txt | 3 +++ tox.ini | 26 ++++++++++++++++++ xcat/bitcoinRPC.py | 62 +++++++++++++++++++++---------------------- 4 files changed, 72 insertions(+), 33 deletions(-) create mode 100644 requirements-test.txt create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index c08d1ce..f3921a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,14 @@ -*.pyc -xcat.egg-info/ +# xcat .tmp/ + +# Python +*.pyc +*.egg-info/ + +# Virtual environment venv/ + +# Unit test / coverage reports +.tox/ +coverage/ +.coverage diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..f6320d3 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,3 @@ +flake8 +pytest +pytest-cov diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e39156e --- /dev/null +++ b/tox.ini @@ -0,0 +1,26 @@ +[tox] +envlist = + py3{5} + +[pytest] +nonrecursedirs = .git .tox venv coverage + +[testenv] +usedevelop = True +deps = + -rrequirements.txt + -rrequirements-test.txt + +commands = + flake8 \ + --ignore=E501,E266 \ + --exclude test.py \ + xcat + pytest \ + -q \ + --junitxml=coverage/unit.xml \ + --cov xcat \ + --cov-report xml:coverage/coverage.xml \ + --cov-report html:coverage/html/ \ + --cov-report term-missing \ + {posargs} diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index e1fb00e..cfa650d 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -14,7 +14,7 @@ from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, P2SHBitcoinAddress from bitcoin.wallet import P2PKHBitcoinAddress -import xcat.utils as utils +# import xcat.utils as utils import logging if sys.version_info.major < 3: @@ -52,15 +52,15 @@ class bitcoinProxy(): print("Redeem transaction with secret not found") return - def parse_secret(self, txid): - raw = zcashd.gettransaction(txid, True)['hex'] - decoded = zcashd.call('decoderawtransaction', raw) - scriptSig = decoded['vin'][0]['scriptSig'] - asm = scriptSig['asm'].split(" ") - # pubkey = asm[1] - secret = utils.x2s(asm[2]) - # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - return secret + # def parse_secret(self, txid): + # raw = zcashd.gettransaction(txid, True)['hex'] + # decoded = zcashd.call('decoderawtransaction', raw) + # scriptSig = decoded['vin'][0]['scriptSig'] + # asm = scriptSig['asm'].split(" ") + # # pubkey = asm[1] + # secret = utils.x2s(asm[2]) + # # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + # return secret def get_keys(self, funder_address, redeemer_address): fundpubkey = CBitcoinAddress(funder_address) @@ -131,27 +131,27 @@ class bitcoinProxy(): return 'empty' # TODO: FIX search for p2sh in block - def search_p2sh(self, block, p2sh): - print("Fetching block...") - blockdata = self.bitcoind.getblock(lx(block)) - print("done fetching block") - txs = blockdata.vtx - print("txs", txs) - for tx in txs: - txhex = b2x(tx.serialize()) - # Using my fork of python-zcashlib to get result of decoderawtransaction - txhex = txhex + '00' - rawtx = zcashd.decoderawtransaction(txhex) - # print('rawtx', rawtx) - print(rawtx) - for vout in rawtx['vout']: - if 'addresses' in vout['scriptPubKey']: - for addr in vout['scriptPubKey']['addresses']: - print("Sent to address:", addr) - if addr == p2sh: - print("Address to p2sh found in transaction! ", - addr) - print("Returning from search_p2sh") + # def search_p2sh(self, block, p2sh): + # print("Fetching block...") + # blockdata = self.bitcoind.getblock(lx(block)) + # print("done fetching block") + # txs = blockdata.vtx + # print("txs", txs) + # for tx in txs: + # txhex = b2x(tx.serialize()) + # # Using my fork of python-zcashlib to get result of decoderawtransaction + # txhex = txhex + '00' + # rawtx = zcashd.decoderawtransaction(txhex) + # # print('rawtx', rawtx) + # print(rawtx) + # for vout in rawtx['vout']: + # if 'addresses' in vout['scriptPubKey']: + # for addr in vout['scriptPubKey']['addresses']: + # print("Sent to address:", addr) + # if addr == p2sh: + # print("Address to p2sh found in transaction! ", + # addr) + # print("Returning from search_p2sh") def get_tx_details(self, txid): # must convert txid string to bytes x(txid) From e375537af4df95fb9e2b95430cc7e56e329bbb16 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 11:12:13 -0600 Subject: [PATCH 19/25] start unit tests for db --- .gitignore | 1 + xcat/tests/test_cli.py | 6 +++-- xcat/tests/unit/test_cli.py | 9 +++++-- xcat/tests/unit/test_db.py | 53 +++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 xcat/tests/unit/test_db.py diff --git a/.gitignore b/.gitignore index f3921a7..d1c8dec 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ venv/ .tox/ coverage/ .coverage +.cache diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 5d012cf..36a7bd3 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -2,13 +2,13 @@ import unittest import xcat.cli as cli from xcat.db import DB from xcat.protocol import Protocol -from xcat.tests.utils import mktrade +import xcat.tests.utils as testutils from xcat.trades import Trade # , Contract class SimpleTestCase(unittest.TestCase): def setUp(self): - self.trade = mktrade() + self.trade = testutils.mktrade() def test_exporttrade(self): self.__class__.hexstr = cli.exporttrade('test') @@ -20,6 +20,7 @@ class SimpleTestCase(unittest.TestCase): class CliTest(SimpleTestCase): + def test_findtrade(self): # trade = cli.findtrade('test') pass @@ -33,6 +34,7 @@ class CliTest(SimpleTestCase): protocol = Protocol() trade = db.get('new') + status = cli.seller_check_status(trade) print("Trade status: {0}\n".format(status)) self.assertEqual(status, 'init') diff --git a/xcat/tests/unit/test_cli.py b/xcat/tests/unit/test_cli.py index 8bf0ecd..1887b1c 100644 --- a/xcat/tests/unit/test_cli.py +++ b/xcat/tests/unit/test_cli.py @@ -7,8 +7,13 @@ import xcat.cli as cli class TestCLI(unittest.TestCase): - def test_save_state(self): - pass + @mock.patch('xcat.cli.DB') + @mock.patch('xcat.cli.utils') + def test_save_state(self, mock_utils, mock_db): + cli.save_state('fake_trade', 'fake_id') + + mock_utils.save.assert_called_with('fake_trade') + mock_db.return_value.create.assert_called_with('fake_trade', 'fake_id') def test_checkSellStatus(self): pass diff --git a/xcat/tests/unit/test_db.py b/xcat/tests/unit/test_db.py new file mode 100644 index 0000000..1e4281b --- /dev/null +++ b/xcat/tests/unit/test_db.py @@ -0,0 +1,53 @@ +import unittest +import unittest.mock as mock +import xcat.db as db +import xcat.tests.utils as utils + + +class TestDB(unittest.TestCase): + + @mock.patch('xcat.db.plyvel') + def setUp(self, mock_plyvel): + self.db = db.DB() + + def test_init(self): + self.assertIsInstance(self.db.db, mock.Mock) + self.assertIsInstance(self.db.preimageDB, mock.Mock) + + @mock.patch('xcat.db.json') + def test_create_with_dict(self, mock_json): + test_id = 'test trade id' + trade_string = 'trade string' + mock_json.dumps.return_value = trade_string + test_trade = utils.test_trade + + self.db.create(test_trade, test_id) + + mock_json.dumps.assert_called_with(test_trade) + self.db.db.put.assert_called_with( + str.encode(test_id), + str.encode(trade_string)) + + def test_create_with_trade(self): + pass + + def test_createByFundtx(self): + pass + + def test_get(self): + pass + + def test_instantiate(self): + pass + + def test_save_secret(self): + pass + + def test_get_secret(self): + pass + + def test_dump(self): + pass + + def test_print_entries(self): + pass From 8dc7f77bae16219681bc6f33b95575a356182857 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 13:29:07 -0600 Subject: [PATCH 20/25] more tests for db, improvements to Trade and DB classes --- xcat/cli.py | 8 +++-- xcat/db.py | 32 +++++++++----------- xcat/tests/test_cli.py | 2 +- xcat/tests/unit/test_db.py | 60 +++++++++++++++++++++++++++++--------- xcat/tests/utils.py | 9 +++++- xcat/trades.py | 36 +++++++++++++++++++---- 6 files changed, 105 insertions(+), 42 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 989ac18..389ca69 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -7,6 +7,7 @@ from xcat.db import DB import xcat.userInput as userInput import xcat.utils as utils from xcat.protocol import Protocol +from xcat.trades import Trade def save_state(trade, tradeid): @@ -26,9 +27,11 @@ def checkSellStatus(tradeid): if status == 'init': userInput.authorize_fund_sell(trade) fund_tx = protocol.fund_sell_contract(trade) + print("Sent fund_tx", fund_tx) trade.sell.fund_tx = fund_tx save_state(trade, tradeid) + elif status == 'buyerFunded': secret = db.get_secret(tradeid) print("Retrieved secret to redeem funds for " @@ -45,10 +48,12 @@ def checkSellStatus(tradeid): save_state(trade, tradeid) # Remove from db? Or just from temporary file storage utils.cleanup(tradeid) + elif status == 'sellerFunded': print("Buyer has not yet funded the contract where you offered to " "buy {0}, please wait for them to complete " "their part.".format(trade.buy.currency)) + elif status == 'sellerRedeemed': print("You have already redeemed the p2sh on the second chain of " "this trade.") @@ -137,10 +142,9 @@ def checkBuyStatus(tradeid): # Import a trade in hex, and save to db def importtrade(tradeid, hexstr=''): - db = DB() protocol = Protocol() trade = utils.x2s(hexstr) - trade = db.instantiate(trade) + trade = Trade(trade) protocol.import_addrs(trade) print(trade.toJSON()) save_state(trade, tradeid) diff --git a/xcat/db.py b/xcat/db.py index e1ea1cf..79bac22 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -16,36 +16,32 @@ class DB(): # Takes dict or obj, saves json str as bytes def create(self, trade, tradeid): - if type(trade) == dict: - trade = json.dumps(trade) - else: + if isinstance(trade, dict): + trade = json.dumps(trade, sort_keys=True, indent=4) + elif isinstance(trade, Trade): trade = trade.toJSON() + else: + raise ValueError('Expected dictionary or Trade object') self.db.put(utils.b(tradeid), utils.b(trade)) # Uses the funding txid as the key to save trade def createByFundtx(self, trade): - trade = trade.toJSON() - # # Save trade by initiating txid - jt = json.loads(trade) - txid = jt['sell']['fund_tx'] + if isinstance(trade, dict): + txid = trade['sell']['fund_tx'] + trade = json.dumps(trade, sort_keys=True, indent=4) + elif isinstance(trade, Trade): + txid = trade.sell.fund_tx + trade = trade.toJSON() + else: + raise ValueError('Expected dictionary or Trade object') self.db.put(utils.b(txid), utils.b(trade)) def get(self, tradeid): rawtrade = self.db.get(utils.b(tradeid)) tradestr = str(rawtrade, 'utf-8') - trade = self.instantiate(tradestr) + trade = Trade(fromJSON=tradestr) return trade - @staticmethod - def instantiate(trade): - if type(trade) == str: - tradestr = json.loads(trade) - trade = Trade( - buy=Contract(tradestr['buy']), - sell=Contract(tradestr['sell']), - commitment=tradestr['commitment']) - return trade - ############################################# ###### Preimages stored by tradeid ########## ############################################# diff --git a/xcat/tests/test_cli.py b/xcat/tests/test_cli.py index 36a7bd3..5864cee 100644 --- a/xcat/tests/test_cli.py +++ b/xcat/tests/test_cli.py @@ -1,8 +1,8 @@ import unittest import xcat.cli as cli +import xcat.tests.utils as testutils from xcat.db import DB from xcat.protocol import Protocol -import xcat.tests.utils as testutils from xcat.trades import Trade # , Contract diff --git a/xcat/tests/unit/test_db.py b/xcat/tests/unit/test_db.py index 1e4281b..b71d3e4 100644 --- a/xcat/tests/unit/test_db.py +++ b/xcat/tests/unit/test_db.py @@ -1,5 +1,6 @@ import unittest import unittest.mock as mock +import json import xcat.db as db import xcat.tests.utils as utils @@ -14,32 +15,63 @@ class TestDB(unittest.TestCase): self.assertIsInstance(self.db.db, mock.Mock) self.assertIsInstance(self.db.preimageDB, mock.Mock) - @mock.patch('xcat.db.json') - def test_create_with_dict(self, mock_json): + def test_create_with_dict(self): test_id = 'test trade id' - trade_string = 'trade string' - mock_json.dumps.return_value = trade_string - test_trade = utils.test_trade - self.db.create(test_trade, test_id) + self.db.create(utils.test_trade_dict, test_id) - mock_json.dumps.assert_called_with(test_trade) self.db.db.put.assert_called_with( str.encode(test_id), - str.encode(trade_string)) + str.encode(str(utils.test_trade))) def test_create_with_trade(self): - pass + test_id = 'test trade id' - def test_createByFundtx(self): - pass + self.db.create(utils.test_trade, test_id) + + self.db.db.put.assert_called_with( + str.encode(test_id), + str.encode(json.dumps(utils.test_trade_dict, + sort_keys=True, + indent=4))) + + def test_create_with_error(self): + with self.assertRaises(ValueError) as context: + self.db.create('this is not valid input', 'trade_id') + + self.assertTrue( + 'Expected dictionary or Trade object' + in str(context.exception)) + + def test_createByFundtx_with_dict(self): + self.db.createByFundtx(utils.test_trade_dict) + + self.db.db.put.assert_called_with( + str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d' + '59511566a2fdb'), + str.encode(str(utils.test_trade))) + + def test_createByFundtx_with_trade(self): + self.db.createByFundtx(utils.test_trade) + + self.db.db.put.assert_called_with( + str.encode('5c5e91a89a08b2d6698f50c9fd9bb2fa22da6c74e226c3dd63d' + '59511566a2fdb'), + str.encode(json.dumps(utils.test_trade_dict, + sort_keys=True, + indent=4))) + + def test_createByFundtx_with_error(self): + with self.assertRaises(ValueError) as context: + self.db.createByFundtx('this is not valid input') + + self.assertTrue( + 'Expected dictionary or Trade object' + in str(context.exception)) def test_get(self): pass - def test_instantiate(self): - pass - def test_save_secret(self): pass diff --git a/xcat/tests/utils.py b/xcat/tests/utils.py index 9f556e9..b6865e2 100644 --- a/xcat/tests/utils.py +++ b/xcat/tests/utils.py @@ -1,6 +1,7 @@ from xcat.db import DB +from xcat.trades import Contract, Trade -test_trade = { +test_trade_dict = { "sell": { "amount": 3.5, "redeemScript": "63a82003d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b68876a9147788b4511a25fba1092e67b307a6dcdb6da125d967022a04b17576a914c7043e62a7391596116f54f6a64c8548e97d3fd96888ac", @@ -21,6 +22,12 @@ test_trade = { "fulfiller": "tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY"}, "commitment": "03d58daab37238604b3e57d4a8bdcffa401dc497a9c1aa4f08ffac81616c22b6"} +test_sell_contract = Contract(test_trade_dict['sell']) +test_buy_contract = Contract(test_trade_dict['buy']) +test_trade = Trade(sell=test_sell_contract, + buy=test_buy_contract, + commitment=test_trade_dict['commitment']) + def mktrade(): db = DB() diff --git a/xcat/trades.py b/xcat/trades.py index 40a2ab2..2857d67 100644 --- a/xcat/trades.py +++ b/xcat/trades.py @@ -1,19 +1,43 @@ import json -class Trade(object): - def __init__(self, sell=None, buy=None, commitment=None): +class Trade(): + def __init__(self, sell=None, buy=None, commitment=None, + fromJSON=None, fromDict=None): '''Create a new trade with buy and sell contracts across two chains''' - self.sell = sell - self.buy = buy - self.commitment = commitment + + if fromJSON is not None and fromDict is None: + if isinstance(fromJSON, str): + fromDict = json.loads(fromJSON) + else: + raise ValueError('Expected json string') + if fromDict is not None: + self.sell = Contract(fromDict['sell']) + self.buy = Contract(fromDict['buy']) + self.commitment = fromDict['commitment'] + else: + self.sell = sell + self.buy = buy + self.commitment = commitment def toJSON(self): return json.dumps( self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + def __str__(self): + return self.toJSON() -class Contract(object): + def __repr__(self): + return 'Trade:\n{0} {1} from {2}\nfor\n{3} {4} from {5}'.format( + self.sell.amount, + self.sell.currency, + self.sell.initiator, + self.buy.amount, + self.buy.currency, + self.buy.initiator) + + +class Contract(): def __init__(self, data): allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', From 182d9caa97e0a5a9a8231cbeefefce35d63b032d Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 14:32:48 -0600 Subject: [PATCH 21/25] more db tests, equality operators for Trade and Contract --- tox.ini | 2 +- xcat/db.py | 4 ++-- xcat/tests/unit/test_db.py | 19 ++++++++++++++++--- xcat/trades.py | 26 ++++++++++++++++++++++---- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/tox.ini b/tox.ini index e39156e..c6897ef 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = commands = flake8 \ - --ignore=E501,E266 \ + --ignore=E501,E266,W503 \ --exclude test.py \ xcat pytest \ diff --git a/xcat/db.py b/xcat/db.py index 79bac22..478e423 100644 --- a/xcat/db.py +++ b/xcat/db.py @@ -1,7 +1,7 @@ import plyvel import json import xcat.utils as utils -from xcat.trades import Trade, Contract +from xcat.trades import Trade class DB(): @@ -63,7 +63,7 @@ class DB(): results = [] with self.db.iterator() as it: for k, v in it: - j = json.loads(utils.x2s(utils.b2x(v))) + j = json.loads(str(v, 'utf-8')) results.append((str(k, 'utf-8'), j)) return results diff --git a/xcat/tests/unit/test_db.py b/xcat/tests/unit/test_db.py index b71d3e4..cc14345 100644 --- a/xcat/tests/unit/test_db.py +++ b/xcat/tests/unit/test_db.py @@ -70,13 +70,26 @@ class TestDB(unittest.TestCase): in str(context.exception)) def test_get(self): - pass + self.db.db.get.return_value = str.encode(utils.test_trade.toJSON()) + + trade = self.db.get('test') + + self.assertEqual(trade, utils.test_trade) def test_save_secret(self): - pass + self.db.save_secret('my life', 'I like black liquorice') + + self.db.preimageDB.put.assert_called_with( + str.encode('my life'), + str.encode('I like black liquorice')) def test_get_secret(self): - pass + self.db.preimageDB.get.return_value = str.encode( + 'I like black liquorice') + + secret = self.db.get_secret('my life') + + self.assertEqual(secret, 'I like black liquorice') def test_dump(self): pass diff --git a/xcat/trades.py b/xcat/trades.py index 2857d67..fbaf3fc 100644 --- a/xcat/trades.py +++ b/xcat/trades.py @@ -36,14 +36,21 @@ class Trade(): self.buy.currency, self.buy.initiator) + def __eq__(self, other): + return (self.sell == other.sell + and self.buy == other.buy + and self.commitment == other.commitment) + class Contract(): + + allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', + 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', + 'redeemblocknum', 'locktime') + def __init__(self, data): - allowed = ('fulfiller', 'initiator', 'currency', 'p2sh', 'amount', - 'fund_tx', 'redeem_tx', 'secret', 'redeemScript', - 'redeemblocknum', 'locktime') for key in data: - if key in allowed: + if key in Contract.allowed: setattr(self, key, data[key]) def get_status(self): @@ -56,3 +63,14 @@ class Contract(): return 'funded' else: return 'empty' + + def __eq__(self, other): + for key in Contract.allowed: + if key in self.__dict__: + if key not in other.__dict__: + return False + if self.__dict__[key] != other.__dict__[key]: + return False + if key in other.__dict__ and key not in self.__dict__: + return False + return True From cf00fd047445527e033138face9d28f4f524b0f6 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 14:43:39 -0600 Subject: [PATCH 22/25] comment out unused lines in bitcoinRPC --- xcat/bitcoinRPC.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index b8d240c..094be07 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -3,6 +3,7 @@ import sys import bitcoin import bitcoin.rpc +from xcat.utils import x2s # from bitcoin import SelectParams from bitcoin.core import b2x, lx, x, COIN, CMutableTxOut from bitcoin.core import CMutableTxIn, CMutableTransaction @@ -52,15 +53,15 @@ class bitcoinProxy(): print("Redeem transaction with secret not found") return - # def parse_secret(self, txid): - # raw = self.bitcoind.call('gettransaction', txid, True)['hex'] - # decoded = self.bitcoind.call('decoderawtransaction', raw) - # scriptSig = decoded['vin'][0]['scriptSig'] - # asm = scriptSig['asm'].split(" ") - # pubkey = asm[1] - # secret = utils.x2s(asm[2]) - # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) - # return secret + def parse_secret(self, txid): + raw = self.bitcoind.call('gettransaction', txid, True)['hex'] + decoded = self.bitcoind.call('decoderawtransaction', raw) + scriptSig = decoded['vin'][0]['scriptSig'] + asm = scriptSig['asm'].split(" ") + # pubkey = asm[1] + secret = x2s(asm[2]) + # redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey)) + return secret def get_keys(self, funder_address, redeemer_address): fundpubkey = CBitcoinAddress(funder_address) From b255ecdda1cd7923462060de2a9c2810c311f47e Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 15:19:33 -0600 Subject: [PATCH 23/25] start test for bitcoinRPC --- xcat/bitcoinRPC.py | 11 +++--- xcat/tests/unit/test_bitcoinRPC.py | 62 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 xcat/tests/unit/test_bitcoinRPC.py diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index 094be07..be47a37 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -4,7 +4,6 @@ import sys import bitcoin import bitcoin.rpc from xcat.utils import x2s -# from bitcoin import SelectParams from bitcoin.core import b2x, lx, x, COIN, CMutableTxOut from bitcoin.core import CMutableTxIn, CMutableTransaction from bitcoin.core.script import CScript, OP_DUP, OP_IF, OP_ELSE, OP_ENDIF @@ -14,8 +13,6 @@ from bitcoin.core.script import OP_CHECKLOCKTIMEVERIFY, OP_SHA256, OP_TRUE from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH from bitcoin.wallet import CBitcoinAddress, P2SHBitcoinAddress from bitcoin.wallet import P2PKHBitcoinAddress - -# import xcat.utils as utils import logging if sys.version_info.major < 3: @@ -27,9 +24,13 @@ FEE = 0.001 * COIN class bitcoinProxy(): def __init__(self, network='regtest', timeout=900): - if network is not 'testnet' and network is not 'mainnet': - network = 'regtest' + if network not in ['testnet', 'mainnet', 'regtest']: + raise ValueError('Allowed networks are regtest, testnet, mainnet.') + if not isinstance(timeout, int) or timeout < 1: + raise ValueError('Timeout should be a positive integer.') + logging.debug("NETWORK in proxy: {0}".format(network)) + self.network = network self.timeout = timeout diff --git a/xcat/tests/unit/test_bitcoinRPC.py b/xcat/tests/unit/test_bitcoinRPC.py new file mode 100644 index 0000000..5865832 --- /dev/null +++ b/xcat/tests/unit/test_bitcoinRPC.py @@ -0,0 +1,62 @@ +import unittest +import unittest.mock as mock +from xcat.bitcoinRPC import bitcoinProxy +import logging + + +@mock.patch('xcat.bitcoinRPC.bitcoin.rpc') +class TestBitcoinProxy(unittest.TestCase): + """Test case for the bitcoinProxy class.""" + + def setUp(self): + logging.disable(logging.CRITICAL) + + @mock.patch('xcat.bitcoinRPC.bitcoin.SelectParams') + def test_init_with_testnet(self, mock_SP, mock_rpc): + """Test bitcoinProxy.__init__""" + + proxy = bitcoinProxy(network='testnet') + + mock_rpc.Proxy.assert_called_with(timeout=900) + mock_SP.assert_called_with('testnet') + self.assertIsInstance(proxy, bitcoinProxy) + + @mock.patch('xcat.bitcoinRPC.bitcoin.SelectParams') + def test_init_with_no_network(self, mock_SP, mock_rpc): + """Test bitcoinProxy.__init__""" + + proxy = bitcoinProxy() + + mock_rpc.Proxy.assert_called_with(timeout=900) + mock_SP.assert_called_with('regtest') + self.assertIsInstance(proxy, bitcoinProxy) + + def test_init_with_invalid(self, mock_rpc): + """Test bitcoinProxy.__init__""" + + with self.assertRaises(ValueError) as context: + proxy = bitcoinProxy(network='invalid input') + self.assertIsNone(proxy) + + self.assertTrue( + 'Allowed networks are regtest, testnet, mainnet.' + in str(context.exception)) + + def test_init_with_invalid_timeout(self, mock_rpc): + """Test bitcoinProxy.__init__""" + + with self.assertRaises(ValueError) as context: + proxy = bitcoinProxy(timeout='invalid input') + self.assertIsNone(proxy) + + self.assertTrue( + 'Timeout should be a positive integer.' + in str(context.exception)) + + with self.assertRaises(ValueError) as context_two: + proxy = bitcoinProxy(timeout=-381) + self.assertIsNone(proxy) + + self.assertTrue( + 'Timeout should be a positive integer.' + in str(context_two.exception)) From e13288f17e506483feefa7a1c7e6e0a50e64d9dd Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 16:21:38 -0600 Subject: [PATCH 24/25] placeholder tests for bitcoinRPC --- xcat/tests/unit/test_bitcoinRPC.py | 71 ++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/xcat/tests/unit/test_bitcoinRPC.py b/xcat/tests/unit/test_bitcoinRPC.py index 5865832..991b038 100644 --- a/xcat/tests/unit/test_bitcoinRPC.py +++ b/xcat/tests/unit/test_bitcoinRPC.py @@ -42,6 +42,14 @@ class TestBitcoinProxy(unittest.TestCase): 'Allowed networks are regtest, testnet, mainnet.' in str(context.exception)) + with self.assertRaises(ValueError) as context_two: + proxy = bitcoinProxy(network=819.3) + self.assertIsNone(proxy) + + self.assertTrue( + 'Allowed networks are regtest, testnet, mainnet.' + in str(context_two.exception)) + def test_init_with_invalid_timeout(self, mock_rpc): """Test bitcoinProxy.__init__""" @@ -60,3 +68,66 @@ class TestBitcoinProxy(unittest.TestCase): self.assertTrue( 'Timeout should be a positive integer.' in str(context_two.exception)) + + def test_validateaddress(self, mock_rpc): + pass + + def test_find_secret(self, mock_rpc): + pass + + def test_parse_secret(self, mock_rpc): + pass + + def test_get_keys(self, mock_rpc): + pass + + def test_privkey(self, mock_rpc): + pass + + def test_hashtimelockcontract(self, mock_rpc): + pass + + def test_fund_htlc(self, mock_rpc): + pass + + def test_check_funds(self, mock_rpc): + pass + + def test_get_fund_status(self, mock_rpc): + pass + + def test_search_p2sh(self, mock_rpc): + pass + + def test_get_tx_details(self, mock_rpc): + pass + + def test_redeem_contract(self, mock_rpc): + pass + + def test_redeem(self, mock_rpc): + pass + + def test_refund(self, mock_rpc): + pass + + def test_parse_script(self, mock_rpc): + pass + + def test_find_redeemblocknum(self, mock_rpc): + pass + + def test_find_redeemAddr(self, mock_rpc): + pass + + def test_find_refundAddr(self, mock_rpc): + pass + + def test_find_transaction_to_address(self, mock_rpc): + pass + + def test_new_bitcoin_addr(self, mock_rpc): + pass + + def test_generate(self, mock_rpc): + pass From 11f9c02043752c1ba4cd4d5459b17fa1ffc682ac Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 15 Sep 2017 17:25:16 -0600 Subject: [PATCH 25/25] copy bitcoinProxy tests for zcashProxy --- xcat/tests/unit/test_zcashRPC.py | 133 +++++++++++++++++++++++++++++++ xcat/zcashRPC.py | 5 ++ 2 files changed, 138 insertions(+) create mode 100644 xcat/tests/unit/test_zcashRPC.py diff --git a/xcat/tests/unit/test_zcashRPC.py b/xcat/tests/unit/test_zcashRPC.py new file mode 100644 index 0000000..d5e9036 --- /dev/null +++ b/xcat/tests/unit/test_zcashRPC.py @@ -0,0 +1,133 @@ +import unittest +import unittest.mock as mock +from xcat.zcashRPC import zcashProxy +import logging + + +@mock.patch('xcat.zcashRPC.zcash.rpc') +class TestBitcoinProxy(unittest.TestCase): + """Test case for the zcashProxy class.""" + + def setUp(self): + logging.disable(logging.CRITICAL) + + @mock.patch('xcat.zcashRPC.zcash.SelectParams') + def test_init_with_testnet(self, mock_SP, mock_rpc): + """Test zcashProxy.__init__""" + + proxy = zcashProxy(network='testnet') + + mock_rpc.Proxy.assert_called_with(timeout=900) + mock_SP.assert_called_with('testnet') + self.assertIsInstance(proxy, zcashProxy) + + @mock.patch('xcat.zcashRPC.zcash.SelectParams') + def test_init_with_no_network(self, mock_SP, mock_rpc): + """Test zcashProxy.__init__""" + + proxy = zcashProxy() + + mock_rpc.Proxy.assert_called_with(timeout=900) + mock_SP.assert_called_with('regtest') + self.assertIsInstance(proxy, zcashProxy) + + def test_init_with_invalid(self, mock_rpc): + """Test zcashProxy.__init__""" + + with self.assertRaises(ValueError) as context: + proxy = zcashProxy(network='invalid input') + self.assertIsNone(proxy) + + self.assertTrue( + 'Allowed networks are regtest, testnet, mainnet.' + in str(context.exception)) + + with self.assertRaises(ValueError) as context_two: + proxy = zcashProxy(network=819.3) + self.assertIsNone(proxy) + + self.assertTrue( + 'Allowed networks are regtest, testnet, mainnet.' + in str(context_two.exception)) + + def test_init_with_invalid_timeout(self, mock_rpc): + """Test zcashProxy.__init__""" + + with self.assertRaises(ValueError) as context: + proxy = zcashProxy(timeout='invalid input') + self.assertIsNone(proxy) + + self.assertTrue( + 'Timeout should be a positive integer.' + in str(context.exception)) + + with self.assertRaises(ValueError) as context_two: + proxy = zcashProxy(timeout=-381) + self.assertIsNone(proxy) + + self.assertTrue( + 'Timeout should be a positive integer.' + in str(context_two.exception)) + + def test_validateaddress(self, mock_rpc): + pass + + def test_find_secret(self, mock_rpc): + pass + + def test_parse_secret(self, mock_rpc): + pass + + def test_get_keys(self, mock_rpc): + pass + + def test_privkey(self, mock_rpc): + pass + + def test_hashtimelockcontract(self, mock_rpc): + pass + + def test_fund_htlc(self, mock_rpc): + pass + + def test_check_funds(self, mock_rpc): + pass + + def test_get_fund_status(self, mock_rpc): + pass + + def test_search_p2sh(self, mock_rpc): + pass + + def test_get_tx_details(self, mock_rpc): + pass + + def test_redeem_contract(self, mock_rpc): + pass + + def test_redeem(self, mock_rpc): + pass + + def test_refund(self, mock_rpc): + pass + + def test_parse_script(self, mock_rpc): + pass + + def test_find_redeemblocknum(self, mock_rpc): + pass + + def test_find_redeemAddr(self, mock_rpc): + pass + + def test_find_refundAddr(self, mock_rpc): + pass + + def test_find_transaction_to_address(self, mock_rpc): + pass + + def test_new_bitcoin_addr(self, mock_rpc): + pass + + def test_generate(self, mock_rpc): + pass diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index aa03f99..8892bf0 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -26,6 +26,11 @@ FEE = 0.001 * COIN class zcashProxy(): def __init__(self, network='regtest', timeout=900): + if network not in ['testnet', 'mainnet', 'regtest']: + raise ValueError('Allowed networks are regtest, testnet, mainnet.') + if not isinstance(timeout, int) or timeout < 1: + raise ValueError('Timeout should be a positive integer.') + self.network = network self.timeout = timeout