Merge pull request #29 from frdwrd/better-tests

Refactoring and unittests
This commit is contained in:
arcalinea 2017-10-19 07:02:02 -07:00 committed by GitHub
commit 76c1729031
19 changed files with 1287 additions and 455 deletions

15
.gitignore vendored
View File

@ -1,4 +1,15 @@
*.pyc
xcat.egg-info/
# xcat
.tmp/
# Python
*.pyc
*.egg-info/
# Virtual environment
venv/
# Unit test / coverage reports
.tox/
coverage/
.coverage
.cache

3
requirements-test.txt Normal file
View File

@ -0,0 +1,3 @@
flake8
pytest
pytest-cov

26
tox.ini Normal file
View File

@ -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,W503 \
--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}

View File

@ -1,33 +1,40 @@
#!/usr/bin/env python3
import sys
import bitcoin
import bitcoin.rpc
from xcat.utils import x2s
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, P2SHBitcoinAddress
from bitcoin.wallet import P2PKHBitcoinAddress
import logging
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.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.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret, P2SHBitcoinAddress, P2PKHBitcoinAddress
FEE = 0.001 * COIN
from xcat.utils import *
import logging
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
SelectParams(self.network)
bitcoin.SelectParams(self.network)
self.bitcoind = bitcoin.rpc.Proxy(timeout=self.timeout)
def validateaddress(self, addr):
@ -41,9 +48,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
@ -52,9 +59,9 @@ class bitcoinProxy():
decoded = self.bitcoind.call('decoderawtransaction', raw)
scriptSig = decoded['vin'][0]['scriptSig']
asm = scriptSig['asm'].split(" ")
pubkey = asm[1]
# pubkey = asm[1]
secret = x2s(asm[2])
redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
# redeemPubkey = P2PKHBitcoinAddress.from_pubkey(x(pubkey))
return secret
def get_keys(self, funder_address, redeemer_address):
@ -77,18 +84,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
@ -103,20 +119,20 @@ 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'
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))
@ -145,9 +161,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")
@ -174,7 +190,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)
@ -182,17 +200,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)
@ -202,7 +222,9 @@ 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])
# Set nSequence and nLockTime
@ -211,15 +233,18 @@ class bitcoinProxy():
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}
@ -229,12 +254,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

View File

@ -1,98 +1,132 @@
import argparse, textwrap
from xcat.utils import *
import xcat.db as db
import xcat.userInput as userInput
from xcat.trades import *
from xcat.protocol import *
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
def save_state(trade, tradeid):
save(trade)
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 = 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)
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 = 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)
cleanup(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))
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):
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh)
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh)
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
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):
sellState = check_fund_status(trade.sell.currency, trade.sell.p2sh)
buyState = check_fund_status(trade.buy.currency, trade.buy.p2sh)
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
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):
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.")
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 = 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)
if secret != None:
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 = 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)
@ -105,18 +139,22 @@ def checkBuyStatus(tradeid):
# 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(tradeid, hexstr=''):
trade = x2s(hexstr)
trade = db.instantiate(trade)
import_addrs(trade)
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): ")
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)
@ -125,12 +163,14 @@ 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)
hexstr = s2x(trade.toJSON())
db = DB()
trade = db.get(tradeid)
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)
@ -140,56 +180,83 @@ def exporttrade(tradeid, wormhole=False):
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 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'
if 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.")
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()
tradeid, trade= initialize_trade(tradeid, conf=kwargs['conf'], network=kwargs['network'])
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 = seller_init(tradeid, trade, network=kwargs['network'])
print("\nUse 'xcat exporttrade [tradeid]' to export the trade and sent to the buyer.\n")
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
def listtrades():
db = DB()
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
@ -199,72 +266,105 @@ 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("-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 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()
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()
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=NETWORK, conf='cli')
else:
newtrade(tradeid, network=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":
tradeid = args.arguments[0]
checkSellStatus(tradeid)
elif command == "step2":
tradeid = args.arguments[0]
checkBuyStatus(tradeid)
elif command == "step3":
# generate(31)
# protocol = Protocol()
# protocol.generate(31)
tradeid = args.arguments[0]
checkSellStatus(tradeid)
elif command == "step4":
# generate(1)
tradeid = args.arguments[0]

View File

@ -1,77 +1,77 @@
import plyvel
from xcat.utils import *
import binascii
import sys
import json
import ast
from xcat.trades import *
import xcat.utils as utils
from xcat.trades import Trade
db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
#############################################
######## Trades stored by tradeid ###########
#############################################
class DB():
# Takes dict or obj, saves json str as bytes
def create(trade, tradeid):
if type(trade) == dict:
trade = json.dumps(trade)
else:
trade = trade.toJSON()
db.put(b(tradeid), b(trade))
def __init__(self):
self.db = plyvel.DB('/tmp/xcatDB', create_if_missing=True)
self.preimageDB = plyvel.DB('/tmp/preimageDB', create_if_missing=True)
# 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(b(txid), b(trade))
#############################################
######## Trades stored by tradeid ###########
#############################################
def get(tradeid):
rawtrade = db.get(b(tradeid))
tradestr = str(rawtrade, 'utf-8')
trade = instantiate(tradestr)
return trade
# Takes dict or obj, saves json str as bytes
def create(self, trade, tradeid):
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))
def instantiate(trade):
if type(trade) == str:
tradestr = json.loads(trade)
trade = Trade(buy=Contract(tradestr['buy']), sell=Contract(tradestr['sell']), commitment=tradestr['commitment'])
# Uses the funding txid as the key to save trade
def createByFundtx(self, trade):
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 = Trade(fromJSON=tradestr)
return trade
#############################################
###### Preimages stored by tradeid ##########
#############################################
#############################################
###### 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))
# 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(tradeid):
secret = preimageDB.get(b(tradeid))
secret = str(secret, 'utf-8')
return secret
def get_secret(self, tradeid):
secret = self.preimageDB.get(utils.b(tradeid))
secret = str(secret, 'utf-8')
return secret
#############################################
########## Dump or view db entries ##########
#############################################
#############################################
########## Dump or view db entries ##########
#############################################
def dump(self):
results = []
with self.db.iterator() as it:
for k, v in it:
j = json.loads(str(v, 'utf-8'))
results.append((str(k, 'utf-8'), j))
return results
def dump():
results = []
with db.iterator() as it:
for k, v in it:
j = json.loads(x2s(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(x2s(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'])

View File

@ -1,219 +1,237 @@
import json
import os, sys
from pprint import pprint
from xcat.utils import *
from xcat.trades import Contract, Trade
import logging
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
import logging
from xcat.db import DB
bitcoinRPC = bitcoinProxy()
zcashRPC = zcashProxy()
def generate(num):
bitcoinRPC.generate(num)
zcashRPC.generate(num)
class Protocol():
def is_myaddr(address):
# Handle different network prefixes
if address[:1] == 'm':
status = bitcoinRPC.validateaddress(address)
else:
status = zcashRPC.validateaddress(address)
logging.debug("Address status: ", status)
if status['isvalid'] is False:
raise ValueError("Invalid address: %s" % address)
elif 'ismine' in status:
status = status['ismine']
return status
def __init__(self):
self.bitcoinRPC = bitcoinProxy()
self.zcashRPC = zcashProxy()
def find_secret_from_fundtx(currency, p2sh, fundtx):
if currency == 'bitcoin':
secret = bitcoinRPC.find_secret(p2sh, fundtx)
elif currency == 'zcash':
secret = zcashRPC.find_secret(p2sh, fundtx)
else:
raise ValueError("Currency not recognized: ", currency)
return secret
def generate(self, num):
self.bitcoinRPC.generate(num)
self.zcashRPC.generate(num)
def import_addrs(trade):
check_fund_status(trade.sell.currency, trade.sell.p2sh)
check_fund_status(trade.buy.currency, trade.buy.p2sh)
def is_myaddr(self, address):
# Handle differnt network prefixes
if address[:1] == 'm':
status = self.bitcoinRPC.validateaddress(address)
else:
status = self.zcashRPC.validateaddress(address)
def check_p2sh(currency, address):
if currency == 'bitcoin':
print("Checking funds in Bitcoin p2sh")
return bitcoinRPC.check_funds(address)
elif currency == 'zcash':
print("Checking funds in Zcash p2sh")
return zcashRPC.check_funds(address)
else:
raise ValueError("Currency not recognized: ", currency)
logging.debug("Address status: ", status)
def check_fund_status(currency, address):
if currency == 'bitcoin':
print("Checking funds in Bitcoin p2sh")
return bitcoinRPC.get_fund_status(address)
elif currency == 'zcash':
print("Checking funds in Zcash p2sh")
return zcashRPC.get_fund_status(address)
else:
raise ValueError("Currency not recognized: ", currency)
if not status['isvalid']:
raise ValueError("Invalid address: %s" % address)
elif 'ismine' in status:
status = status['ismine']
# print("Address {0} is mine: {1}".format(address, status))
return status
# 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 find_secret_from_fundtx(self, currency, p2sh, fundtx):
if currency == 'bitcoin':
secret = self.bitcoinRPC.find_secret(p2sh, fundtx)
elif currency == 'zcash':
secret = self.zcashRPC.find_secret(p2sh, fundtx)
else:
raise ValueError('Currency not recognized: %s' % currency)
return secret
def create_htlc(currency, funder, redeemer, commitment, locktime):
if currency == 'bitcoin':
sell_p2sh = bitcoinRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
elif currency == 'zcash':
sell_p2sh = zcashRPC.hashtimelockcontract(funder, redeemer, commitment, locktime)
else:
raise ValueError("Currency not recognized: ", currency)
return sell_p2sh
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 fund_htlc(currency, p2sh, amount):
if currency == 'bitcoin':
txid = bitcoinRPC.fund_htlc(p2sh, amount)
elif currency == 'zcash':
txid = zcashRPC.fund_htlc(p2sh, amount)
else:
raise ValueError("Currency not recognized: ", currency)
return txid
def check_p2sh(self, currency, address):
if currency == 'bitcoin':
print("Checking funds in Bitcoin p2sh")
return self.bitcoinRPC.check_funds(address)
elif currency == 'zcash':
print("Checking funds in Zcash p2sh")
return self.zcashRPC.check_funds(address)
else:
raise ValueError('Currency not recognized: %s' % currency)
def redeem_p2sh(contract, secret):
currency = contract.currency
if currency == 'bitcoin':
res = bitcoinRPC.redeem_contract(contract, secret)
elif currency == 'zcash':
res = zcashRPC.redeem_contract(contract, secret)
else:
raise ValueError("Currency not recognized: ", currency)
return res
def check_fund_status(self, currency, address):
if currency == 'bitcoin':
print("Checking funds in Bitcoin p2sh")
return self.bitcoinRPC.get_fund_status(address)
elif currency == 'zcash':
print("Checking funds in Zcash p2sh")
return self.zcashRPC.get_fund_status(address)
else:
raise ValueError('Currency not recognized: %s' % currency)
def refund_contract(contract):
currency = contract.currency
if currency == 'bitcoin':
res = bitcoinRPC.refund(contract)
elif currency == 'zcash':
res = zcashRPC.refund(contract)
else:
raise ValueError("Currency not recognized: ", currency)
return res
# 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 parse_secret(currency, txid):
if currency == 'bitcoin':
secret = bitcoinRPC.parse_secret(txid)
elif currency == 'zcash':
secret = zcashRPC.parse_secret(txid)
else:
raise ValueError("Currency not recognized: ", currency)
return secret
def create_htlc(self, currency, funder, redeemer, commitment, locktime):
if currency == 'bitcoin':
sell_p2sh = self.bitcoinRPC.hashtimelockcontract(
funder, redeemer, commitment, locktime)
elif currency == 'zcash':
sell_p2sh = self.zcashRPC.hashtimelockcontract(
funder, redeemer, commitment, locktime)
else:
raise ValueError('Currency not recognized: %s' % currency)
return sell_p2sh
def fund_contract(contract):
txid = fund_htlc(contract.currency, contract.p2sh, contract.amount)
return txid
def fund_htlc(self, currency, p2sh, amount):
if currency == 'bitcoin':
txid = self.bitcoinRPC.fund_htlc(p2sh, amount)
elif currency == 'zcash':
txid = self.zcashRPC.fund_htlc(p2sh, amount)
else:
raise ValueError('Currency not recognized: %s' % currency)
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 fund_contract(self, contract):
txid = self.fund_htlc(
contract.currency, contract.p2sh, contract.amount)
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 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_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)
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)
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))
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)
save(trade)
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))
#### Main functions related to user flow from command line
def seller_redeem_p2sh(trade, secret):
buy = trade.buy
userInput.authorize_seller_redeem(buy)
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 = redeem_p2sh(trade.buy, secret)
print("You have redeemed {0} {1}!".format(buy.amount, buy.currency))
return txs
utils.save(trade)
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 redeem_p2sh(self, contract, secret):
currency = contract.currency
if currency == 'bitcoin':
res = self.bitcoinRPC.redeem_contract(contract, secret)
elif currency == 'zcash':
res = self.zcashRPC.redeem_contract(contract, secret)
else:
raise ValueError('Currency not recognized: %s' % currency)
return res
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 refund_contract(self, contract):
currency = contract.currency
if currency == 'bitcoin':
res = self.bitcoinRPC.refund(contract)
elif currency == 'zcash':
res = self.zcashRPC.refund(contract)
else:
raise ValueError('Currency not recognized: %s', currency)
return res
# 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 parse_secret(self, currency, txid):
if currency == 'bitcoin':
secret = self.bitcoinRPC.parse_secret(txid)
elif currency == 'zcash':
secret = self.zcashRPC.parse_secret(txid)
else:
raise ValueError('Currency not recognized: %s', currency)
return secret
def seller_init(tradeid, trade, network):
secret = generate_password()
db.save_secret(tradeid, secret)
print("Generated a secret preimage to lock funds. This will only be stored locally: {0}".format(secret))
def seller_redeem_p2sh(self, trade, secret):
buy = trade.buy
userInput.authorize_seller_redeem(buy)
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...")
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
# create the p2sh addrs
create_sell_p2sh(trade, hash_of_secret, sell_locktime)
create_buy_p2sh(trade, hash_of_secret, buy_locktime)
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']
trade.commitment = b2x(hash_of_secret)
print("TRADE after seller init: {0}".format(trade.toJSON()))
return trade
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()
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
buy_locktime = 10 # Must be more than first tx
print("Creating pay-to-script-hash for sell contract...")
# 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 {0}".format(trade.toJSON()))
return trade

View File

@ -1,36 +1,45 @@
import unittest
import xcat.cli as cli
import xcat.db as db
from xcat.tests.utils import mktrade
from xcat.trades import Trade, Contract
import xcat.tests.utils as testutils
from xcat.db import DB
from xcat.protocol import Protocol
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')
self.assertTrue(int(self.hexstr, 16))
def test_importtrade(self):
trade = cli.importtrade('test', self.__class__.hexstr)
# trade = cli.importtrade('test', self.__class__.hexstr)
pass
class CliTest(SimpleTestCase):
def test_findtrade(self):
trade = cli.findtrade('test')
# trade = cli.findtrade('test')
pass
def test_newtrade(self):
trade = cli.newtrade('new', conf='regtest')
self.assertTrue(isinstance(trade, Trade))
def test_fundsell(self):
db = DB()
protocol = Protocol()
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)
fund_tx = protocol.fund_sell_contract(trade)
print("Sent fund_tx", fund_tx)
# def test_fundbuy(self):

View File

@ -1,21 +1,25 @@
import xcat.database as db
import unittest, json
import unittest
import xcat.trades as trades
from xcat.db import 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'])
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'])
db.create(trade, 'test')
def test_get(self):
trade = db.get('test')
# trade = db.get('test')
print("Trade")
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,133 @@
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))
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__"""
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))
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

116
xcat/tests/unit/test_cli.py Normal file
View File

@ -0,0 +1,116 @@
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):
@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
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()

View File

@ -0,0 +1,98 @@
import unittest
import unittest.mock as mock
import json
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)
def test_create_with_dict(self):
test_id = 'test trade id'
self.db.create(utils.test_trade_dict, test_id)
self.db.db.put.assert_called_with(
str.encode(test_id),
str.encode(str(utils.test_trade)))
def test_create_with_trade(self):
test_id = 'test trade id'
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):
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):
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):
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
def test_print_entries(self):
pass

View File

@ -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

View File

@ -1,8 +1,36 @@
import xcat.db as db
from xcat.db import DB
from xcat.trades import Contract, Trade
test_trade_dict = {
"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"}
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'])
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 = DB()
db.create(test_trade, 'test')
trade = db.get('test')
return trade

View File

@ -1,21 +1,56 @@
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'''
self.sell = sell
self.buy = buy
self.commitment = commitment
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'''
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)
return json.dumps(
self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
def __str__(self):
return self.toJSON()
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)
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')
class Contract(object):
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):
@ -28,3 +63,14 @@ class Contract(object):
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

View File

@ -1,17 +1,20 @@
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():
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)? ")
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':
@ -20,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)
@ -34,40 +39,66 @@ 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.")
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}
return 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))

View File

@ -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:

View File

@ -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'

View File

@ -1,29 +1,40 @@
#!/usr/bin/env python3
import sys
import zcash
import zcash.rpc
# 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
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.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
FEE = 0.001 * COIN
from xcat.utils import x2s
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
SelectParams(self.network)
zcash.SelectParams(self.network)
self.zcashd = zcash.rpc.Proxy(timeout=self.timeout)
def validateaddress(self, addr):
@ -47,23 +58,34 @@ 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
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)
@ -75,13 +97,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'
@ -108,7 +130,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 +177,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 +206,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 +216,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 +236,9 @@ 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])
# Set nSequence and nLockTime
txin.nSequence = 0
@ -224,10 +251,11 @@ class zcashProxy():
txin.scriptSig = CScript([sig, privkey.pub, OP_FALSE, redeemScript])
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}