zbxcat/xcat/cli.py

334 lines
13 KiB
Python

import argparse
import textwrap
import subprocess
import os
import logging
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
class CLI():
def __init__(self):
self.db = DB()
self.protocol = Protocol()
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)
print("Sent fund_tx", fund_tx)
trade.sell.fund_tx = fund_tx
self.db.create(trade, tradeid)
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.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:
print("Importing trade using magic-wormhole failed.")
# 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:
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:
if self.protocol.is_myaddr(contract.fulfiller):
return 'fulfiller'
else:
raise ValueError('You are not a participant in this contract.')
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))
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")
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('''\
== Trades ==
newtrade "tradeid" - create a new trade
checktrade "tradeid"- check for actions to be taken on an existing trade
importtrade "hexstr" - import an existing trade from a hex string
exporttrade "tradeid" - export the data of an existing trade as a hex string. Takes the tradeid as an argument
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("-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)
logging.basicConfig(format='%(levelname)s: %(message)s',
level=numeric_level)
else:
logging.basicConfig(format='%(levelname)s: %(message)s',
level='INFO')
if hasattr(args, 'network'):
NETWORK = args.network
else:
NETWORK = 'testnet'
command = args.command
if command == 'importtrade':
if args.wormhole:
cli.wormhole_importtrade()
else:
if len(args.arguments) != 2:
utils.throw("Usage: importtrade [tradeid] [hexstring]")
tradeid = args.arguments[0]
hexstr = args.arguments[1]
cli.importtrade(tradeid, hexstr)
elif command == 'exporttrade':
if len(args.arguments) < 1:
utils.throw("Usage: exporttrade [tradeid]")
tradeid = args.arguments[0]
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]
cli.findtrade(key)
elif command == 'checktrade':
if len(args.arguments) < 1:
utils.throw("Usage: checktrade [tradeid]")
tradeid = args.arguments[0]
cli.checktrade(tradeid)
elif command == 'listtrades':
cli.listtrades()
# TODO: function to tell if tradeid already exists for newtrade
elif command == 'newtrade':
if len(args.arguments) < 1:
utils.throw("Usage: newtrade [tradeid]")
tradeid = args.arguments[0]
if args.conf is None:
conf = 'cli'
else:
conf = args.conf
cli.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]
cli.checkSellStatus(tradeid)
elif command == "step2":
tradeid = args.arguments[0]
cli.checkBuyStatus(tradeid)
elif command == "step3":
# cli.protocol.generate(31)
tradeid = args.arguments[0]
cli.checkSellStatus(tradeid)
elif command == "step4":
# cli.protocol.generate(1)
tradeid = args.arguments[0]
cli.checkBuyStatus(tradeid)