Skip to content
This repository was archived by the owner on May 16, 2019. It is now read-only.

Commit f2dcdc6

Browse files
author
Tom Galloway
committed
Merge branch 'master' into market_listeners_tests
2 parents c48e11a + e649a30 commit f2dcdc6

File tree

16 files changed

+297
-196
lines changed

16 files changed

+297
-196
lines changed

api/restapi.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from keys.keychain import KeyChain
2626
from dht.utils import digest
2727
from market.profile import Profile
28-
from market.contracts import Contract
28+
from market.contracts import Contract, check_order_for_payment
2929
from net.upnp import PortMapper
3030

3131
DEFAULT_RECORDS_COUNT = 20
@@ -1105,19 +1105,26 @@ def get_order(self, request):
11051105

11061106
if os.path.exists(DATA_FOLDER + "purchases/unfunded/" + request.args["order_id"][0] + ".json"):
11071107
file_path = DATA_FOLDER + "purchases/unfunded/" + request.args["order_id"][0] + ".json"
1108+
status = self.db.purchases.get_status(request.args["order_id"][0])
11081109
elif os.path.exists(DATA_FOLDER + "purchases/in progress/" + request.args["order_id"][0] + ".json"):
11091110
file_path = DATA_FOLDER + "purchases/in progress/" + request.args["order_id"][0] + ".json"
1111+
status = self.db.purchases.get_status(request.args["order_id"][0])
11101112
elif os.path.exists(DATA_FOLDER + "purchases/trade receipts/" + request.args["order_id"][0] + ".json"):
11111113
file_path = DATA_FOLDER + "purchases/trade receipts/" + request.args["order_id"][0] + ".json"
1114+
status = self.db.purchases.get_status(request.args["order_id"][0])
11121115
elif os.path.exists(DATA_FOLDER + "store/contracts/unfunded/" + request.args["order_id"][0] + ".json"):
11131116
file_path = DATA_FOLDER + "store/contracts/unfunded/" + request.args["order_id"][0] + ".json"
1117+
status = self.db.sales.get_status(request.args["order_id"][0])
11141118
elif os.path.exists(DATA_FOLDER + "store/contracts/in progress/" + request.args["order_id"][0] + ".json"):
11151119
file_path = DATA_FOLDER + "store/contracts/in progress/" + request.args["order_id"][0] + ".json"
1120+
status = self.db.sales.get_status(request.args["order_id"][0])
11161121
elif os.path.exists(DATA_FOLDER +
11171122
"store/contracts/trade receipts/" + request.args["order_id"][0] + ".json"):
11181123
file_path = DATA_FOLDER + "store/contracts/trade receipts/" + request.args["order_id"][0] + ".json"
1124+
status = self.db.sales.get_status(request.args["order_id"][0])
11191125
elif os.path.exists(DATA_FOLDER + "cases/" + request.args["order_id"][0] + ".json"):
11201126
file_path = DATA_FOLDER + "cases/" + request.args["order_id"][0] + ".json"
1127+
status = 4
11211128
else:
11221129
request.write(json.dumps({}, indent=4))
11231130
request.finish()
@@ -1126,6 +1133,12 @@ def get_order(self, request):
11261133
with open(file_path, 'r') as filename:
11271134
order = json.load(filename, object_pairs_hook=OrderedDict)
11281135

1136+
self.protocol.blockchain.refresh_connection()
1137+
if status == 0:
1138+
check_order_for_payment(request.args["order_id"][0], self.db, self.protocol.blockchain,
1139+
self.mserver.protocol.get_notification_listener(),
1140+
self.protocol.testnet)
1141+
11291142
def return_order():
11301143
request.setHeader('content-type', "application/json")
11311144
request.write(str(bleach.clean(json.dumps(order, indent=4), tags=ALLOWED_TAGS)))
@@ -1311,6 +1324,7 @@ def refund(self, request):
13111324
request.finish()
13121325
return server.NOT_DONE_YET
13131326

1327+
13141328
class RestAPI(Site):
13151329

13161330
def __init__(self, mserver, kserver, openbazaar_protocol, username, password,

config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
PROTOCOL_VERSION = 14
1616
CONFIG_FILE = join(os.getcwd(), 'ob.cfg')
1717

18-
# FIXME probably a better way to do this. This curretly checks two levels deep
18+
# FIXME probably a better way to do this. This curretly checks two levels deep.
1919
for i in range(2):
2020
if not isfile(CONFIG_FILE):
2121
paths = CONFIG_FILE.rsplit('/', 2)
2222
CONFIG_FILE = join(paths[0], paths[2])
2323

2424
DEFAULTS = {
2525
# Default project config file may now remove these items
26-
'data_folder': 'OpenBazaar', # FIXME change to 'None' when issue #163 is resolved
26+
'data_folder': None,
2727
'ksize': '20',
2828
'alpha': '3',
2929
'transaction_fee': '10000',
@@ -91,7 +91,7 @@ def _platform_agnostic_data_folder(data_folder):
9191
name = join('Library', 'Application Support', 'OpenBazaar')
9292
elif _is_linux():
9393
name = '.openbazaar'
94-
else: # TODO add clauses for Windows, and BSD
94+
else:
9595
name = 'OpenBazaar'
9696

9797
return name

db/datastore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ def get_all(self):
952952
def get_unfunded(self):
953953
conn = Database.connect_database(self.PATH)
954954
cursor = conn.cursor()
955-
cursor.execute('''SELECT id FROM sales WHERE status=0''')
955+
cursor.execute('''SELECT id, timestamp FROM sales WHERE status=0''')
956956
ret = cursor.fetchall()
957957
conn.close()
958958
return ret

dht/network.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pickle
99
import httplib
10+
import random
1011
from binascii import hexlify
1112
from twisted.internet.task import LoopingCall
1213
from twisted.internet import defer, reactor, task
@@ -83,7 +84,9 @@ def refreshTable(self):
8384
(per section 2.3 of the paper).
8485
"""
8586
ds = []
86-
for rid in self.protocol.getRefreshIDs():
87+
refresh_ids = self.protocol.getRefreshIDs()
88+
refresh_ids.append(digest(random.getrandbits(255))) # random node so we get more diversity
89+
for rid in refresh_ids:
8790
node = Node(rid)
8891
nearest = self.protocol.router.findNeighbors(node, self.alpha)
8992
spider = NodeSpiderCrawl(self.protocol, node, nearest, self.ksize, self.alpha)

interfaces.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class MessageProcessor(Interface):
1313
multiplexer = Attribute("""The main `ConnectionMultiplexer` protocol.
1414
We pass it in here so we can send datagrams from this class.""")
1515

16-
def receive_message(datagram, sender, connection, ban_score):
16+
def receive_message(datagram, sender, connection):
1717
"""
1818
Called by OpenBazaarProtocol when it receives a new message intended for this processor.
1919
@@ -25,8 +25,6 @@ def receive_message(datagram, sender, connection, ban_score):
2525
2626
connection: the txrudp connection to the peer who sent the message. To respond directly to the peer call
2727
connection.send_message()
28-
29-
ban_score: a `net.dos.BanScore` object for tracking a peer's behavior.
3028
"""
3129

3230
def connect_multiplexer(multiplexer):

keys/keychain.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ def __init__(self, database, callback=None, heartbeat_server=None):
1212
self.db = database
1313
guid_keys = self.db.keys.get_key("guid")
1414
if guid_keys is None:
15-
heartbeat_server.set_status("generating GUID")
16-
threading.Thread(target=self.create_keychain, args=[callback, heartbeat_server]).start()
15+
if heartbeat_server:
16+
heartbeat_server.set_status("generating GUID")
17+
threading.Thread(target=self.create_keychain, args=[callback]).start()
1718
else:
1819
g = GUID.from_privkey(guid_keys[0])
1920
self.guid = g.guid
@@ -23,10 +24,10 @@ def __init__(self, database, callback=None, heartbeat_server=None):
2324
self.bitcoin_master_privkey, self.bitcoin_master_pubkey = self.db.keys.get_key("bitcoin")
2425
self.encryption_key = self.signing_key.to_curve25519_private_key()
2526
self.encryption_pubkey = self.verify_key.to_curve25519_public_key()
26-
if callback is not None:
27+
if callable(callback):
2728
callback(self)
2829

29-
def create_keychain(self, callback, heartbeat_server):
30+
def create_keychain(self, callback=None):
3031
"""
3132
The guid generation can take a while. While it's doing that we will
3233
open a port to allow a UI to connect and listen for generation to
@@ -46,5 +47,5 @@ def create_keychain(self, callback, heartbeat_server):
4647

4748
self.encryption_key = self.signing_key.to_curve25519_private_key()
4849
self.encryption_pubkey = self.verify_key.to_curve25519_public_key()
49-
callback(self, True)
50-
50+
if callable(callback):
51+
callback(self, True)

market/contracts.py

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,45 +1257,50 @@ def check_unfunded_for_payment(db, libbitcoin_client, notification_listener, tes
12571257
Run through the unfunded contracts in our database and query the
12581258
libbitcoin server to see if they received a payment.
12591259
"""
1260-
def check(order_id):
1261-
try:
1262-
if os.path.exists(DATA_FOLDER + "purchases/unfunded/" + order_id[0] + ".json"):
1263-
file_path = DATA_FOLDER + "purchases/unfunded/" + order_id[0] + ".json"
1264-
is_purchase = True
1265-
elif os.path.exists(DATA_FOLDER + "store/contracts/unfunded/" + order_id[0] + ".json"):
1266-
file_path = DATA_FOLDER + "store/contracts/unfunded/" + order_id[0] + ".json"
1267-
is_purchase = False
1268-
with open(file_path, 'r') as filename:
1269-
order = json.load(filename, object_pairs_hook=OrderedDict)
1270-
c = Contract(db, contract=order, testnet=testnet)
1271-
c.blockchain = libbitcoin_client
1272-
c.notification_listener = notification_listener
1273-
c.is_purchase = is_purchase
1274-
addr = c.contract["buyer_order"]["order"]["payment"]["address"]
1275-
def history_fetched(ec, history):
1276-
if not ec:
1277-
# pylint: disable=W0612
1278-
# pylint: disable=W0640
1279-
for objid, txhash, index, height, value in history:
1280-
def cb_txpool(ec, result):
1281-
if ec:
1282-
libbitcoin_client.fetch_transaction(txhash, cb_chain)
1283-
else:
1284-
c.on_tx_received(None, None, None, None, result)
1285-
1286-
def cb_chain(ec, result):
1287-
if not ec:
1288-
c.on_tx_received(None, None, None, None, result)
1289-
1290-
libbitcoin_client.fetch_txpool_transaction(txhash, cb_txpool)
1291-
1292-
libbitcoin_client.fetch_history2(addr, history_fetched)
1293-
except Exception:
1294-
pass
1260+
current_time = time.time()
12951261
libbitcoin_client.refresh_connection()
12961262
purchases = db.purchases.get_unfunded()
12971263
for purchase in purchases:
1298-
check(purchase)
1264+
if current_time - purchase[1] <= 86400:
1265+
check_order_for_payment(purchase[0], db, libbitcoin_client, notification_listener, testnet)
12991266
sales = db.sales.get_unfunded()
13001267
for sale in sales:
1301-
check(sale)
1268+
if current_time - sale[1] <= 86400:
1269+
check_order_for_payment(sale[0], db, libbitcoin_client, notification_listener, testnet)
1270+
1271+
1272+
def check_order_for_payment(order_id, db, libbitcoin_client, notification_listener, testnet=False):
1273+
try:
1274+
if os.path.exists(DATA_FOLDER + "purchases/unfunded/" + order_id[0] + ".json"):
1275+
file_path = DATA_FOLDER + "purchases/unfunded/" + order_id[0] + ".json"
1276+
is_purchase = True
1277+
elif os.path.exists(DATA_FOLDER + "store/contracts/unfunded/" + order_id[0] + ".json"):
1278+
file_path = DATA_FOLDER + "store/contracts/unfunded/" + order_id[0] + ".json"
1279+
is_purchase = False
1280+
with open(file_path, 'r') as filename:
1281+
order = json.load(filename, object_pairs_hook=OrderedDict)
1282+
c = Contract(db, contract=order, testnet=testnet)
1283+
c.blockchain = libbitcoin_client
1284+
c.notification_listener = notification_listener
1285+
c.is_purchase = is_purchase
1286+
addr = c.contract["buyer_order"]["order"]["payment"]["address"]
1287+
def history_fetched(ec, history):
1288+
if not ec:
1289+
# pylint: disable=W0612
1290+
# pylint: disable=W0640
1291+
for objid, txhash, index, height, value in history:
1292+
def cb_txpool(ec, result):
1293+
if ec:
1294+
libbitcoin_client.fetch_transaction(txhash, cb_chain)
1295+
else:
1296+
c.on_tx_received(None, None, None, None, result)
1297+
1298+
def cb_chain(ec, result):
1299+
if not ec:
1300+
c.on_tx_received(None, None, None, None, result)
1301+
1302+
libbitcoin_client.fetch_txpool_transaction(txhash, cb_txpool)
1303+
1304+
libbitcoin_client.fetch_history2(addr, history_fetched)
1305+
except Exception:
1306+
pass

market/moderation.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,6 @@ def close_dispute(resolution_json, db, message_listener, notification_listener,
157157
moderator_pubkey = unhexlify(moderator["pubkeys"]["guid"])
158158
moderator_avatar = unhexlify(moderator["avatar"])
159159

160-
print json.dumps(resolution_json, indent=4)
161-
162160
verify_key = nacl.signing.VerifyKey(moderator_pubkey)
163161
verify_key.verify(json.dumps(resolution_json["dispute_resolution"]["resolution"], indent=4),
164162
base64.b64decode(resolution_json["dispute_resolution"]["signature"]))
@@ -182,7 +180,7 @@ def close_dispute(resolution_json, db, message_listener, notification_listener,
182180
p.type = PlaintextMessage.Type.Value("DISPUTE_CLOSE")
183181
p.message = str(resolution_json["dispute_resolution"]["resolution"]["decision"])
184182
p.timestamp = int(time.time())
185-
p.avatar_hash = unhexlify(str(moderator_avatar))
183+
p.avatar_hash = moderator_avatar
186184

187185
message_listener.notify(p, "")
188186
notification_listener.notify(moderator_guid, moderator_handle, "dispute_close", order_id,

market/network.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,7 @@ def get_result(result):
997997
d = self.protocol.callGetRatings(node_to_ask, listing_hash)
998998
return d.addCallback(get_result)
999999

1000-
def refund(self, order_id, reason):
1000+
def refund(self, order_id):
10011001
"""
10021002
Refund the given order_id. If this is a direct payment he transaction will be
10031003
immediately broadcast to the Bitcoin network otherwise the refund message sent
@@ -1023,7 +1023,6 @@ def refund(self, order_id, reason):
10231023

10241024
refund_json = {"refund": {}}
10251025
refund_json["refund"]["order_id"] = order_id
1026-
refund_json["refund"]["reason"] = reason
10271026
if "moderator" in contract["buyer_order"]["order"]:
10281027
sigs = tx.create_signature(vendor_priv, redeem_script)
10291028
refund_json["refund"]["value"] = tx.get_out_value()

market/protocol.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
__author__ = 'chris'
22

3+
import bitcointools
34
import json
5+
import os
6+
import pickle
47
import nacl.signing
58
import nacl.utils
69
import nacl.encoding
710
import nacl.hash
11+
from binascii import unhexlify
812
from collections import OrderedDict
13+
from config import DATA_FOLDER
914
from interfaces import MessageProcessor, BroadcastListener, MessageListener, NotificationListener
1015
from keys.bip32utils import derive_childkey
16+
from keys.keychain import KeyChain
1117
from log import Logger
1218
from market.contracts import Contract
1319
from market.moderation import process_dispute, close_dispute
1420
from market.profile import Profile
21+
from market.transactions import BitcoinTransaction
1522
from nacl.public import PublicKey, Box
1623
from net.rpcudp import RPCProtocol
1724
from protos.message import GET_CONTRACT, GET_IMAGE, GET_PROFILE, GET_LISTINGS, GET_USER_METADATA,\
@@ -346,6 +353,73 @@ def rpc_get_ratings(self, sender, listing_hash=None):
346353
self.log.warning("could not load ratings for contract %s" % a)
347354
return None
348355

356+
def rpc_refund(self, sender, pubkey, encrypted):
357+
try:
358+
box = Box(self.signing_key.to_curve25519_private_key(), PublicKey(pubkey))
359+
refund = box.decrypt(encrypted)
360+
refund_json = json.loads(refund, object_pairs_hook=OrderedDict)
361+
order_id = refund_json["order_id"]
362+
363+
file_path = DATA_FOLDER + "purchases/in progress/" + order_id + ".json"
364+
with open(file_path, 'r') as filename:
365+
order = json.load(filename, object_pairs_hook=OrderedDict)
366+
order["refund"] = refund_json["refund"]
367+
368+
if "txid" not in refund_json:
369+
outpoints = pickle.loads(self.db.sales.get_outpoint(order_id))
370+
refund_address = order["buyer_order"]["order"]["refund_address"]
371+
redeem_script = order["buyer_order"]["order"]["payment"]["redeem_script"]
372+
tx = BitcoinTransaction.make_unsigned(outpoints, refund_address,
373+
testnet=self.multiplexer.testnet,
374+
out_value=long(refund_json["refund"]["value"]))
375+
chaincode = order["buyer_order"]["order"]["payment"]["chaincode"]
376+
masterkey_b = bitcointools.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey)
377+
buyer_priv = derive_childkey(masterkey_b, chaincode, bitcointools.MAINNET_PRIVATE)
378+
buyer_sigs = tx.create_signature(buyer_priv, redeem_script)
379+
vendor_sigs = refund_json["refund"]["signature(s)"]
380+
381+
signatures = []
382+
for i in range(len(outpoints)):
383+
for vendor_sig in vendor_sigs:
384+
if vendor_sig["index"] == i:
385+
v_signature = vendor_sig["signature"]
386+
for buyer_sig in buyer_sigs:
387+
if buyer_sig["index"] == i:
388+
b_signature = buyer_sig["signature"]
389+
signature_obj = {"index": i, "signatures": [b_signature, v_signature]}
390+
signatures.append(signature_obj)
391+
392+
tx.multisign(signatures, redeem_script)
393+
tx.broadcast(self.multiplexer.blockchain)
394+
self.log.info("Broadcasting refund tx %s to network" % tx.get_hash())
395+
396+
self.db.sales.update_status(order_id, 7)
397+
file_path = DATA_FOLDER + "purchases/trade receipts/" + order_id + ".json"
398+
with open(file_path, 'w') as outfile:
399+
outfile.write(json.dumps(order, indent=4))
400+
file_path = DATA_FOLDER + "purchases/in progress/" + order_id + ".json"
401+
if os.path.exists(file_path):
402+
os.remove(file_path)
403+
404+
title = order["vendor_offer"]["listing"]["item"]["title"]
405+
if "image_hashes" in order["vendor_offer"]["listing"]["item"]:
406+
image_hash = unhexlify(order["vendor_offer"]["listing"]["item"]["image_hashes"][0])
407+
else:
408+
image_hash = ""
409+
buyer_guid = self.contract["buyer_order"]["order"]["id"]["guid"]
410+
if "blockchain_id" in self.contract["buyer_order"]["order"]["id"]:
411+
handle = self.contract["buyer_order"]["order"]["id"]["blockchain_id"]
412+
else:
413+
handle = ""
414+
self.get_notification_listener().notify(buyer_guid, handle, "refund", order_id, title, image_hash)
415+
416+
self.router.addContact(sender)
417+
self.log.info("order %s refunded by vendor" % refund_json["refund"]["order_id"])
418+
return ["True"]
419+
except Exception:
420+
self.log.error("unable to parse refund message from %s" % sender)
421+
return ["False"]
422+
349423
def callGetContract(self, nodeToAsk, contract_hash):
350424
d = self.get_contract(nodeToAsk, contract_hash)
351425
return d.addCallback(self.handleCallResponse, nodeToAsk)
@@ -444,6 +518,7 @@ def get_notification_listener(self):
444518
return listener
445519
except DoesNotImplement:
446520
pass
521+
447522
def get_message_listener(self):
448523
for listener in self.listeners:
449524
try:

0 commit comments

Comments
 (0)