Merge pull request #29 from frdwrd/better-tests
Refactoring and unittests
This commit is contained in:
commit
76c1729031
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
flake8
|
||||
pytest
|
||||
pytest-cov
|
|
@ -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}
|
|
@ -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
|
||||
|
|
248
xcat/cli.py
248
xcat/cli.py
|
@ -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]
|
||||
|
|
124
xcat/db.py
124
xcat/db.py
|
@ -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'])
|
||||
|
|
396
xcat/protocol.py
396
xcat/protocol.py
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
Loading…
Reference in New Issue