From 1f3a65cd421d1e3c5da247e8a019c046400dec53 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 22 Sep 2017 18:37:00 -0700 Subject: [PATCH 1/5] Add option to pass trade data in as json on cli --- xcat/cli.py | 48 ++++++++++------------------------ xcat/protocol.py | 68 +++++++++++++++++++++++++++--------------------- xcat/xcatconf.py | 5 ++++ 3 files changed, 58 insertions(+), 63 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 5190b57..645de99 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -228,19 +228,13 @@ 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=conf, - network=network) + tradeid, trade = protocol.initialize_trade(tradeid, conf=conf, network=network) print("New trade created: {0}".format(trade)) trade = protocol.seller_init(tradeid, trade, network=network) - 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 send to the buyer.\n") save_state(trade, tradeid) return trade @@ -266,26 +260,15 @@ 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("-d", "--debug", action="store_true", help="Enable debug mode. Defaults to false") + parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole") + parser.add_argument("-c", "--conf", action="store", help="Use trade data in conf file ('testnet' or 'regtest'), or pass trade data in on cli as json.") + 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() + print(args) if hasattr(args, 'debug'): numeric_level = getattr(logging, 'DEBUG', None) @@ -341,31 +324,28 @@ def main(): utils.throw("Usage: newtrade [tradeid]") tradeid = args.arguments[0] if args.conf is None: - newtrade(tradeid, network=NETWORK, conf='cli') + conf = 'cli' else: - newtrade(tradeid, network=NETWORK, conf=args.conf) + conf = args.conf + newtrade(tradeid, network=NETWORK, conf=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) + generate(1) tradeid = args.arguments[0] checkBuyStatus(tradeid) diff --git a/xcat/protocol.py b/xcat/protocol.py index dae278b..5b8a8be 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -5,6 +5,9 @@ from xcat.xcatconf import ADDRS from xcat.trades import Contract, Trade from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy +import logging +import json +<<<<<<< HEAD from xcat.db import DB @@ -186,35 +189,6 @@ class Protocol(): "{0} {1}!".format(buy.amount, buy.currency)) return txs - 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] - - # 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 seller_init(self, tradeid, trade, network): db = DB() secret = utils.generate_password() @@ -235,3 +209,39 @@ class Protocol(): trade.commitment = utils.b2x(hash_of_secret) print("TRADE after seller init {0}".format(trade.toJSON())) return 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: + print("Conf in init trade", conf) + if conf == 'testnet' or conf == 'regtest': + # If json is not passed on cli, use ADDR obj from xcatconf.py + conf = ADDRS[conf] + else: + # Allow for passing in multiple trades at a time + conf = json.loads(conf)[0] + init_addrs = conf['initiator'] + fulfill_addrs = conf['fulfiller'] + amounts = 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] + + # 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 diff --git a/xcat/xcatconf.py b/xcat/xcatconf.py index 6efb053..07dd244 100644 --- a/xcat/xcatconf.py +++ b/xcat/xcatconf.py @@ -23,3 +23,8 @@ ADDRS = { "amounts": {'buy': {'currency': 'zcash', 'amount': 0.02}, 'sell': {'currency': 'bitcoin', 'amount': 0.01}} } } + +NETWORK = 'testnet' + +# Pass regtest trade data in on command line +# '[{"initiator": {"bitcoin": "mvc56qCEVj6p57xZ5URNC3v7qbatudHQ9b", "zcash": "tmTF7LMLjvEsGdcepWPUsh4vgJNrKMWwEyc"}, "fulfiller": {"bitcoin": "moRt56gJQGDNK46Y6fYy2HbooKnQXrTGDN", "zcash": "tmK3rGzHDqa78MCwEicx9VcY9ZWX9gCF2nd"}, "amounts": {"buy": {"currency": "zcash", "amount": 0.02}, "sell": {"currency": "bitcoin", "amount": 0.01}}}]' From 2d902c9c6c0ce17153a1873ca4d5f387db7004b4 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Fri, 22 Sep 2017 19:28:44 -0700 Subject: [PATCH 2/5] Add auto mode to skip user input --- xcat/cli.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 645de99..55257e9 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -10,6 +10,8 @@ from xcat.protocol import Protocol from xcat.trades import Trade +MODE = {'auto': False} + def save_state(trade, tradeid): db = DB() utils.save(trade) @@ -25,18 +27,31 @@ def checkSellStatus(tradeid): print("Trade status: {0}\n".format(status)) if status == 'init': +<<<<<<< HEAD userInput.authorize_fund_sell(trade) fund_tx = protocol.fund_sell_contract(trade) +======= + if MODE['auto'] == False: + userInput.authorize_fund_sell(trade) + fund_tx = fund_sell_contract(trade) +>>>>>>> Add auto mode to skip user input print("Sent fund_tx", fund_tx) trade.sell.fund_tx = fund_tx save_state(trade, tradeid) elif status == 'buyerFunded': secret = db.get_secret(tradeid) +<<<<<<< HEAD print("Retrieved secret to redeem funds for " "{0}: {1}".format(tradeid, secret)) txs = protocol.seller_redeem_p2sh(trade, secret) +======= + print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) + if MODE['auto'] == False: + userInput.authorize_seller_redeem(buy) + txs = seller_redeem_p2sh(trade, secret) +>>>>>>> Add auto mode to skip user input if 'redeem_tx' in txs: trade.buy.redeem_tx = txs['redeem_tx'] print("Redeem tx: ", txs['redeem_tx']) @@ -112,8 +127,13 @@ def checkBuyStatus(tradeid): print("This trade is complete, both sides redeemed.") elif status == 'sellerFunded': print("One active trade available, fulfilling buyer contract...") +<<<<<<< HEAD input("Type 'enter' to allow this program to send funds on your " "behalf.") +======= + if MODE['auto'] == False: + input("Type 'enter' to allow this program to send funds on your behalf.") +>>>>>>> Add auto mode to skip user input print("Trade commitment", trade.commitment) # if verify_p2sh(trade): fund_tx = protocol.fund_contract(trade.buy) @@ -262,13 +282,16 @@ def main(): ''')) parser.add_argument("command", action="store", help="list commands") parser.add_argument("arguments", action="store", nargs="*", help="add arguments") + parser.add_argument("-a", "--auto", action="store_true", help="Authorize automatic execution of trade sequence without user input.") parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode. Defaults to false") parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole") parser.add_argument("-c", "--conf", action="store", help="Use trade data in conf file ('testnet' or 'regtest'), or pass trade data in on cli as json.") 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() - print(args) + + print("State of auto input: ", args.auto) + MODE['auto'] = args.auto if hasattr(args, 'debug'): numeric_level = getattr(logging, 'DEBUG', None) @@ -335,17 +358,25 @@ def main(): # Ad hoc testing of workflow starts here elif command == "step1": + MODE['auto'] = True tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step2": + MODE['auto'] = True tradeid = args.arguments[0] checkBuyStatus(tradeid) elif command == "step3": +<<<<<<< HEAD # protocol = Protocol() # protocol.generate(31) +======= + MODE['auto'] = True + # generate(31) +>>>>>>> Add auto mode to skip user input tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step4": - generate(1) + MODE['auto'] = True + # generate(1) tradeid = args.arguments[0] checkBuyStatus(tradeid) From 6840c11d5559d30d9e4ddba9d1944d54779b7b1d Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 19 Oct 2017 07:26:47 -0700 Subject: [PATCH 3/5] Remove merge conflicts from master --- xcat/cli.py | 35 ++--------------------------------- xcat/protocol.py | 1 - 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/xcat/cli.py b/xcat/cli.py index 55257e9..645de99 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -10,8 +10,6 @@ from xcat.protocol import Protocol from xcat.trades import Trade -MODE = {'auto': False} - def save_state(trade, tradeid): db = DB() utils.save(trade) @@ -27,31 +25,18 @@ def checkSellStatus(tradeid): print("Trade status: {0}\n".format(status)) if status == 'init': -<<<<<<< HEAD userInput.authorize_fund_sell(trade) fund_tx = protocol.fund_sell_contract(trade) -======= - if MODE['auto'] == False: - userInput.authorize_fund_sell(trade) - fund_tx = fund_sell_contract(trade) ->>>>>>> Add auto mode to skip user input print("Sent fund_tx", fund_tx) trade.sell.fund_tx = fund_tx save_state(trade, tradeid) elif status == 'buyerFunded': secret = db.get_secret(tradeid) -<<<<<<< HEAD print("Retrieved secret to redeem funds for " "{0}: {1}".format(tradeid, secret)) txs = protocol.seller_redeem_p2sh(trade, secret) -======= - print("Retrieved secret to redeem funds for {0}: {1}".format(tradeid, secret)) - if MODE['auto'] == False: - userInput.authorize_seller_redeem(buy) - txs = seller_redeem_p2sh(trade, secret) ->>>>>>> Add auto mode to skip user input if 'redeem_tx' in txs: trade.buy.redeem_tx = txs['redeem_tx'] print("Redeem tx: ", txs['redeem_tx']) @@ -127,13 +112,8 @@ def checkBuyStatus(tradeid): print("This trade is complete, both sides redeemed.") elif status == 'sellerFunded': print("One active trade available, fulfilling buyer contract...") -<<<<<<< HEAD input("Type 'enter' to allow this program to send funds on your " "behalf.") -======= - if MODE['auto'] == False: - input("Type 'enter' to allow this program to send funds on your behalf.") ->>>>>>> Add auto mode to skip user input print("Trade commitment", trade.commitment) # if verify_p2sh(trade): fund_tx = protocol.fund_contract(trade.buy) @@ -282,16 +262,13 @@ def main(): ''')) parser.add_argument("command", action="store", help="list commands") parser.add_argument("arguments", action="store", nargs="*", help="add arguments") - parser.add_argument("-a", "--auto", action="store_true", help="Authorize automatic execution of trade sequence without user input.") parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode. Defaults to false") parser.add_argument("-w", "--wormhole", action="store_true", help="Transfer trade data through magic-wormhole") parser.add_argument("-c", "--conf", action="store", help="Use trade data in conf file ('testnet' or 'regtest'), or pass trade data in on cli as json.") 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() - - print("State of auto input: ", args.auto) - MODE['auto'] = args.auto + print(args) if hasattr(args, 'debug'): numeric_level = getattr(logging, 'DEBUG', None) @@ -358,25 +335,17 @@ def main(): # Ad hoc testing of workflow starts here elif command == "step1": - MODE['auto'] = True tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step2": - MODE['auto'] = True tradeid = args.arguments[0] checkBuyStatus(tradeid) elif command == "step3": -<<<<<<< HEAD # protocol = Protocol() # protocol.generate(31) -======= - MODE['auto'] = True - # generate(31) ->>>>>>> Add auto mode to skip user input tradeid = args.arguments[0] checkSellStatus(tradeid) elif command == "step4": - MODE['auto'] = True - # generate(1) + generate(1) tradeid = args.arguments[0] checkBuyStatus(tradeid) diff --git a/xcat/protocol.py b/xcat/protocol.py index 5b8a8be..bfb7578 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -7,7 +7,6 @@ from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy import logging import json -<<<<<<< HEAD from xcat.db import DB From 9f2147b01f3d11eb59c866e7c38cb82e83706c5f Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 19 Oct 2017 10:56:10 -0700 Subject: [PATCH 4/5] Refactor cli into CLI class, rm db call from protocol --- xcat/cli.py | 454 ++++++++++++++++++++++----------------------- xcat/protocol.py | 11 +- xcat/tests/test.py | 103 ---------- xcat/utils.py | 16 +- 4 files changed, 226 insertions(+), 358 deletions(-) delete mode 100644 xcat/tests/test.py diff --git a/xcat/cli.py b/xcat/cli.py index 645de99..c24a4dd 100644 --- a/xcat/cli.py +++ b/xcat/cli.py @@ -9,246 +9,229 @@ import xcat.utils as utils from xcat.protocol import Protocol from xcat.trades import Trade +class CLI(): + def __init__(self): + self.db = DB() + self.protocol = Protocol() -def save_state(trade, tradeid): - db = DB() - utils.save(trade) - db.create(trade, tradeid) + def checkSellStatus(self, tradeid): + trade = self.db.get(tradeid) + status = self.seller_check_status(trade) + print("Trade status: {0}\n".format(status)) + if status == 'init': + userInput.authorize_fund_sell(trade) + fund_tx = self.protocol.fund_sell_contract(trade) -def checkSellStatus(tradeid): - db = DB() - protocol = Protocol() + print("Sent fund_tx", fund_tx) + trade.sell.fund_tx = fund_tx + self.db.create(trade, 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 = 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 = protocol.seller_redeem_p2sh(trade, secret) - if 'redeem_tx' in txs: - trade.buy.redeem_tx = txs['redeem_tx'] - print("Redeem tx: ", txs['redeem_tx']) - if 'refund_tx' in txs: - trade.buy.redeem_tx = txs['refund_tx'] - print("Buyer refund tx: ", txs['refund_tx']) - 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 - 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.") - - -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': - 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 - elif sellState == 'funded' and buyState == 'funded': - return 'buyerFunded' # step2 - elif sellState == 'empty' and buyState == 'empty': - if hasattr(trade.sell, 'redeem_tx'): - 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) - if sellState == 'funded' and buyState == 'empty': - return 'sellerFunded' # step1 - elif sellState == 'funded' and hasattr(trade.buy, 'redeem_tx'): - return 'sellerRedeemed' # step3 - # TODO: How does seller get buyer funded tx? - elif sellState == 'funded' and buyState == 'funded': - return 'buyerFunded' # step2 - elif sellState == 'empty' and buyState == 'empty': - if hasattr(trade.buy, 'redeem_tx'): - return 'buyerRedeemed' # step4 - else: - return 'init' # step0 - - -def checkBuyStatus(tradeid): - db = DB() - protocol = Protocol() - trade = db.get(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.") - 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.") - print("Trade commitment", trade.commitment) - # if verify_p2sh(trade): - 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 = protocol.find_secret_from_fundtx(trade.buy.currency, - trade.buy.p2sh, - trade.buy.fund_tx) - if secret is not None: - print("Found secret on blockchain in seller's redeem tx: ", secret) - txs = protocol.redeem_p2sh(trade.sell, secret) + elif status == 'buyerFunded': + secret = self.db.get_secret(tradeid) + print("Retrieved secret to redeem funds for " + "{0}: {1}".format(tradeid, secret)) + txs = self.protocol.seller_redeem_p2sh(trade, secret) if 'redeem_tx' in txs: - trade.sell.redeem_tx = txs['redeem_tx'] - print("Redeem txid: ", trade.sell.redeem_tx) - elif 'refund_tx' in txs: - trade.sell.redeem_tx = txs['refund_tx'] - print("Refund tx: ", txs['refund_tx']) - save_state(trade, tradeid) - print("XCAT trade complete!") + trade.buy.redeem_tx = txs['redeem_tx'] + print("Redeem tx: ", txs['redeem_tx']) + if 'refund_tx' in txs: + trade.buy.redeem_tx = txs['refund_tx'] + print("Buyer refund tx: ", txs['refund_tx']) + txs = self.protocol.refund_contract(trade.sell) # Refund to seller + print("Your refund txid: ", txs['refund_tx']) + self.db.create(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.") + + + def buyer_check_status(self, trade): + sellState = self.protocol.check_fund_status( + trade.sell.currency, trade.sell.p2sh) + buyState = self.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? + elif sellState == 'funded' and hasattr(trade.buy, 'fund_tx'): + return 'sellerRedeemed' # step3 + elif sellState == 'funded' and buyState == 'funded': + return 'buyerFunded' # step2 + elif sellState == 'empty' and buyState == 'empty': + if hasattr(trade.sell, 'redeem_tx'): + return 'buyerRedeemed' # step4 + else: + return 'init' + + + def seller_check_status(self, trade): + sellState = self.protocol.check_fund_status( + trade.sell.currency, trade.sell.p2sh) + buyState = self.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'): + return 'sellerRedeemed' # step3 + # TODO: How does seller get buyer funded tx? + elif sellState == 'funded' and buyState == 'funded': + return 'buyerFunded' # step2 + elif sellState == 'empty' and buyState == 'empty': + if hasattr(trade.buy, 'redeem_tx'): + return 'buyerRedeemed' # step4 + else: + return 'init' # step0 + + + def checkBuyStatus(self, tradeid): + trade = self.db.get(tradeid) + status = self.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.") + 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.") + print("Trade commitment", trade.commitment) + # if verify_p2sh(trade): + fund_tx = self.protocol.fund_contract(trade.buy) + print("\nYou sent this funding tx: ", fund_tx) + trade.buy.fund_tx = fund_tx + self.db.create(trade, tradeid) + elif status == 'sellerRedeemed': + secret = self.protocol.find_secret_from_fundtx(trade.buy.currency, + trade.buy.p2sh, + trade.buy.fund_tx) + if secret is not None: + print("Found secret on blockchain in seller's redeem tx: ", secret) + txs = self.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) + elif 'refund_tx' in txs: + trade.sell.redeem_tx = txs['refund_tx'] + print("Refund tx: ", txs['refund_tx']) + self.db.create(trade, tradeid) + print("XCAT trade complete!") + else: + # Search if tx has been refunded from p2sh + print("Secret not found in redeemtx") + + + # Import a trade in hex, and save to db + def importtrade(self, tradeid, hexstr=''): + trade = utils.x2s(hexstr) + trade = Trade(trade) + self.protocol.import_addrs(trade) + print(trade.toJSON()) + self.db.create(trade, tradeid) + + + def wormhole_importtrade(self): + res = subprocess.call('wormhole receive', shell=True) + if res == 0: + tradeid = input("Enter filename of received trade data to import " + "(printed on line above): ") + with open(tradeid) as infile: + hexstr = infile.readline().strip() + self.importtrade(tradeid, hexstr) + print("Successfully imported trade using magic-wormhole") + os.remove(tradeid) else: - # Search if tx has been refunded from p2sh - print("Secret not found in redeemtx") + print("Importing trade using magic-wormhole failed.") -# Import a trade in hex, and save to db -def importtrade(tradeid, hexstr=''): - protocol = Protocol() - trade = utils.x2s(hexstr) - trade = Trade(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: - 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) - print("Successfully imported trade using magic-wormhole") - os.remove(tradeid) - else: - print("Importing trade using magic-wormhole failed.") - - -# Export a trade by its tradeid -def exporttrade(tradeid, wormhole=False): - db = DB() - trade = db.get(tradeid) - hexstr = utils.s2x(trade.toJSON()) - if wormhole: - tradefile = os.path.join(utils.ROOT_DIR, '.tmp/{0}'.format(tradeid)) - print(tradefile) - with open(tradefile, '+w') as outfile: - outfile.write(hexstr) - print("Exporting trade to buyer using magic wormhole.") - subprocess.call('wormhole send {0}'.format(tradefile), shell=True) - else: - print(hexstr) - return hexstr - - -def findtrade(tradeid): - db = DB() - trade = db.get(tradeid) - print(trade.toJSON()) - return trade - - -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): - return 'test' + # Export a trade by its tradeid + def exporttrade(self, tradeid, wormhole=False): + trade = self.db.get(tradeid) + hexstr = utils.s2x(trade.toJSON()) + if wormhole: + tradefile = os.path.join(utils.ROOT_DIR, '.tmp/{0}'.format(tradeid)) + print(tradefile) + with open(tradefile, '+w') as outfile: + outfile.write(hexstr) + print("Exporting trade to buyer using magic wormhole.") + subprocess.call('wormhole send {0}'.format(tradefile), shell=True) else: - return 'initiator' - else: - if protocol.is_myaddr(contract.fulfiller): - return 'fulfiller' + print(hexstr) + return hexstr + + def findtrade(self, tradeid): + trade = self.db.get(tradeid) + print(trade.toJSON()) + return trade + + def find_role(self, contract): + # When regtest created both addrs on same machine, role is both. + if self.protocol.is_myaddr(contract.initiator): + if self.protocol.is_myaddr(contract.fulfiller): + return 'test' + else: + return 'initiator' else: - raise ValueError('You are not a participant in this contract.') + if self.protocol.is_myaddr(contract.fulfiller): + return 'fulfiller' + else: + raise ValueError('You are not a participant in this contract.') -def checktrade(tradeid): - db = DB() - 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.") - checkSellStatus(tradeid) - checkBuyStatus(tradeid) - checkSellStatus(tradeid) - checkBuyStatus(tradeid) - elif find_role(trade.sell) == 'initiator': - print("You are the seller in this trade.") - # role = 'seller' - checkSellStatus(tradeid) - else: - print("You are the buyer in this trade.") - # role = 'buyer' - checkBuyStatus(tradeid) + def checktrade(self, tradeid): + print("In checktrade") + trade = self.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.") + self.checkSellStatus(tradeid) + self.checkBuyStatus(tradeid) + self.checkSellStatus(tradeid) + self.checkBuyStatus(tradeid) + elif find_role(trade.sell) == 'initiator': + print("You are the seller in this trade.") + # role = 'seller' + self.checkSellStatus(tradeid) + else: + print("You are the buyer in this trade.") + # role = 'buyer' + self.checkBuyStatus(tradeid) + def newtrade(self, tradeid, **kwargs): + 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 = self.protocol.initialize_trade(tradeid, conf=conf, network=network) + print("New trade created: {0}".format(trade)) -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=conf, network=network) - print("New trade created: {0}".format(trade)) + trade, secret = self.protocol.seller_init(tradeid, trade, network=network) + self.db.save_secret(tradeid, secret) + print("\nGenerated a secret preimage to lock funds. This will only " + "be stored locally: {0}".format(secret)) + print("\nUse 'xcat exporttrade [tradeid]' to export the trade and send to the buyer.\n") - trade = protocol.seller_init(tradeid, trade, network=network) - print("\nUse 'xcat exporttrade [tradeid]' to export the trade and send to the buyer.\n") - - save_state(trade, tradeid) - return trade - - -def listtrades(): - db = DB() - print("Trades") - trade_list = db.dump() - for trade in trade_list: - print("{0}: {1}".format(trade[0], trade[1])) + self.db.create(trade, tradeid) + return trade + def listtrades(self): + print("Trades") + trade_list = self.db.dump() + for trade in trade_list: + print("{0}: {1}".format(trade[0], trade[1])) def main(): + cli = CLI() parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description=textwrap.dedent('''\ @@ -287,35 +270,35 @@ def main(): if command == 'importtrade': if args.wormhole: - wormhole_importtrade() + cli.wormhole_importtrade() else: if len(args.arguments) != 2: utils.throw("Usage: importtrade [tradeid] [hexstring]") tradeid = args.arguments[0] hexstr = args.arguments[1] - importtrade(tradeid, hexstr) + cli.importtrade(tradeid, hexstr) elif command == 'exporttrade': if len(args.arguments) < 1: utils.throw("Usage: exporttrade [tradeid]") tradeid = args.arguments[0] - exporttrade(tradeid, args.wormhole) + cli.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) + cli.findtrade(key) elif command == 'checktrade': if len(args.arguments) < 1: utils.throw("Usage: checktrade [tradeid]") tradeid = args.arguments[0] - checktrade(tradeid) + cli.checktrade(tradeid) elif command == 'listtrades': - listtrades() + cli.listtrades() # TODO: function to tell if tradeid already exists for newtrade @@ -327,7 +310,7 @@ def main(): conf = 'cli' else: conf = args.conf - newtrade(tradeid, network=NETWORK, conf=conf) + cli.newtrade(tradeid, network=NETWORK, conf=conf) elif command == "daemon": # TODO: not implemented @@ -336,16 +319,15 @@ def main(): # Ad hoc testing of workflow starts here elif command == "step1": tradeid = args.arguments[0] - checkSellStatus(tradeid) + cli.checkSellStatus(tradeid) elif command == "step2": tradeid = args.arguments[0] - checkBuyStatus(tradeid) + cli.checkBuyStatus(tradeid) elif command == "step3": - # protocol = Protocol() - # protocol.generate(31) + # cli.protocol.generate(31) tradeid = args.arguments[0] - checkSellStatus(tradeid) + cli.checkSellStatus(tradeid) elif command == "step4": - generate(1) + # cli.protocol.generate(1) tradeid = args.arguments[0] - checkBuyStatus(tradeid) + cli.checkBuyStatus(tradeid) diff --git a/xcat/protocol.py b/xcat/protocol.py index bfb7578..80c58ae 100644 --- a/xcat/protocol.py +++ b/xcat/protocol.py @@ -5,13 +5,9 @@ from xcat.xcatconf import ADDRS from xcat.trades import Contract, Trade from xcat.bitcoinRPC import bitcoinProxy from xcat.zcashRPC import zcashProxy -import logging import json -from xcat.db import DB - class Protocol(): - def __init__(self): self.bitcoinRPC = bitcoinProxy() self.zcashRPC = zcashProxy() @@ -189,12 +185,7 @@ class Protocol(): return txs 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 " - "be stored locally: {0}".format(secret)) - hash_of_secret = utils.sha256(secret) # TODO: Implement locktimes and mock block passage of time sell_locktime = 20 @@ -207,7 +198,7 @@ class Protocol(): trade.commitment = utils.b2x(hash_of_secret) print("TRADE after seller init {0}".format(trade.toJSON())) - return trade + return trade, secret def initialize_trade(self, tradeid, **kwargs): trade = Trade() diff --git a/xcat/tests/test.py b/xcat/tests/test.py deleted file mode 100644 index 1cc40ca..0000000 --- a/xcat/tests/test.py +++ /dev/null @@ -1,103 +0,0 @@ -import xcat.zcash -import xcat.bitcoin -from xcat import * - -htlcTrade = Trade() -print("Starting test of xcat...") - -def get_initiator_addresses(): - return {'bitcoin': 'myfFr5twPYNwgeXyjCmGcrzXtCmfmWXKYp', 'zcash': 'tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ'} - -def get_fulfiller_addresses(): - return {'bitcoin': 'mrQzUGU1dwsWRx5gsKKSDPNtrsP65vCA3Z', 'zcash': 'tmTjZSg4pX2Us6V5HttiwFZwj464fD2ZgpY'} - -def initiate(trade): - # Get amounts - amounts = {"sell": {"currency": "bitcoin", "amount": "0.5"}, "buy": {"currency": "zcash", "amount": "1.12"}} - sell = amounts['sell'] - buy = amounts['buy'] - sell_currency = sell['currency'] - buy_currency = buy['currency'] - # Get addresses - init_addrs = get_initiator_addresses() - sell['initiator'] = init_addrs[sell_currency] - buy['initiator'] = init_addrs[buy_currency] - fulfill_addrs = get_fulfiller_addresses() - sell['fulfiller'] = fulfill_addrs[sell_currency] - buy['fulfiller'] = fulfill_addrs[buy_currency] - # initializing contract classes with addresses, currencies, and amounts - trade.sell = Contract(sell) - trade.buy = Contract(buy) - print(trade.sell.__dict__) - print(trade.buy.__dict__) - - secret = generate_password() - print("Generating secret to lock funds:", secret) - save_secret(secret) - # TODO: Implement locktimes and mock block passage of time - sell_locktime = 2 - buy_locktime = 4 # Must be more than first tx - - create_sell_p2sh(trade, secret, sell_locktime) - txid = fund_sell_contract(trade) - print("Sent") - create_buy_p2sh(trade, secret, buy_locktime) - -def 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) - - if buy_p2sh_balance == 0: - print("Buy amt:", buy.amount) - txid = fund_buy_contract(trade) - print("Fund tx txid:", txid) - else: - raise ValueError("Sell p2sh not funded, buyer cannot redeem") - -def redeem_one(trade): - buy = trade.buy - if trade.sell.get_status() == 'redeemed': - raise RuntimeError("Sell contract status was already redeemed before seller could redeem buyer's tx") - else: - secret = get_secret() - print("GETTING SECRET IN TEST:", secret) - tx_type, txid = redeem_p2sh(trade.buy, secret) - print("\nTX Type", tx_type) - setattr(trade.buy, tx_type, txid) - save(trade) - print("You have redeemed {0} {1}!".format(buy.amount, buy.currency)) - -def redeem_two(trade): - if trade.sell.get_status() == 'redeemed': - raise RuntimeError("Sell contract was redeemed before buyer could retrieve funds") - elif trade.buy.get_status() == 'refunded': - print("buy was refunded to buyer") - else: - # Buy contract is where seller disclosed secret in redeeming - if trade.buy.currency == 'bitcoin': - secret = bXcat.parse_secret(trade.buy.redeem_tx) - else: - secret = zXcat.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) - -def generate_blocks(num): - bXcat.generate(num) - zXcat.generate(num) - -initiate(htlcTrade) -fulfill(htlcTrade) - -generate_blocks(6) - -redeem_one(htlcTrade) -redeem_two(htlcTrade) - -# addr = CBitcoinAddress('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ') -# print(addr) -# # print(b2x('tmFRXyju7ANM7A9mg75ZjyhFW1UJEhUPwfQ')) -# print(b2x(addr)) diff --git a/xcat/utils.py b/xcat/utils.py index 94e3c27..2f61d81 100644 --- a/xcat/utils.py +++ b/xcat/utils.py @@ -92,11 +92,6 @@ if not os.path.exists(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) @@ -113,16 +108,19 @@ def erase_trade(): except: pass - +# Dumping trade to json file for development and debugging 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) +def save_trade(trade): + with open(xcatjson, 'w+') as outfile: + json.dump(trade, outfile) # Remove tmp files when trade is complete def cleanup(tradeid): From cd9ae67750cd2c2cda94d73206a7c728ddd0b711 Mon Sep 17 00:00:00 2001 From: Jay Graber Date: Thu, 19 Oct 2017 11:03:34 -0700 Subject: [PATCH 5/5] Raise ValueError when commitment is not a string --- xcat/bitcoinRPC.py | 5 ++--- xcat/zcashRPC.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xcat/bitcoinRPC.py b/xcat/bitcoinRPC.py index be47a37..4b9bc57 100644 --- a/xcat/bitcoinRPC.py +++ b/xcat/bitcoinRPC.py @@ -67,8 +67,6 @@ class bitcoinProxy(): def get_keys(self, funder_address, redeemer_address): fundpubkey = CBitcoinAddress(funder_address) redeempubkey = CBitcoinAddress(redeemer_address) - # fundpubkey = self.bitcoind.getnewaddress() - # redeempubkey = self.bitcoind.getnewaddress() return fundpubkey, redeempubkey def privkey(self, address): @@ -79,7 +77,8 @@ class bitcoinProxy(): redeemerAddr = CBitcoinAddress(redeemer) if type(commitment) == str: commitment = x(commitment) - # h = sha256(secret) + else: + raise ValueError("Commitment was not a string: {0}".format(commitment)) blocknum = self.bitcoind.getblockcount() print("Current blocknum on Bitcoin: ", blocknum) redeemblocknum = blocknum + locktime diff --git a/xcat/zcashRPC.py b/xcat/zcashRPC.py index 8892bf0..8c3e840 100644 --- a/xcat/zcashRPC.py +++ b/xcat/zcashRPC.py @@ -53,7 +53,8 @@ class zcashProxy(): redeemerAddr = CBitcoinAddress(redeemer) if type(commitment) == str: commitment = x(commitment) - # h = sha256(secret) + else: + raise ValueError("Commitment was not a string: {0}".format(commitment)) blocknum = self.zcashd.getblockcount() print("Current blocknum on Zcash: ", blocknum) redeemblocknum = blocknum + locktime