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

Commit ace9941

Browse files
committed
Add error message to dispute_close
1 parent 2a377ad commit ace9941

File tree

4 files changed

+196
-118
lines changed

4 files changed

+196
-118
lines changed

api/restapi.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,14 +1181,22 @@ def dispute_contract(self, request):
11811181
@authenticated
11821182
def close_dispute(self, request):
11831183
try:
1184-
self.mserver.close_dispute(request.args["order_id"][0],
1185-
request.args["resolution"][0],
1186-
request.args["buyer_percentage"][0],
1187-
request.args["vendor_percentage"][0],
1188-
request.args["moderator_percentage"][0],
1189-
request.args["moderator_address"][0])
1190-
request.write(json.dumps({"success": True}, indent=4))
1191-
request.finish()
1184+
def cb(resp):
1185+
if resp:
1186+
request.write(json.dumps({"success": True}, indent=4))
1187+
request.finish()
1188+
else:
1189+
request.write(json.dumps({"success": False, "reason": resp}, indent=4))
1190+
request.finish()
1191+
1192+
d = self.mserver.close_dispute(request.args["order_id"][0],
1193+
request.args["resolution"][0],
1194+
request.args["buyer_percentage"][0],
1195+
request.args["vendor_percentage"][0],
1196+
request.args["moderator_percentage"][0],
1197+
request.args["moderator_address"][0])
1198+
1199+
d.addCallback(cb)
11921200
return server.NOT_DONE_YET
11931201
except Exception, e:
11941202
request.write(json.dumps({"success": False, "reason": e.message}, indent=4))

market/network.py

Lines changed: 170 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -783,112 +783,118 @@ def close_dispute(self, order_id, resolution, buyer_percentage,
783783
Called when a moderator closes a dispute. It will create a payout transactions refunding both
784784
parties and send it to them in a dispute_close message.
785785
"""
786-
try:
787-
if not self.protocol.multiplexer.blockchain.connected:
788-
raise Exception("Libbitcoin server not online")
789-
with open(DATA_FOLDER + "cases/" + order_id + ".json", "r") as filename:
790-
contract = json.load(filename, object_pairs_hook=OrderedDict)
786+
if not self.protocol.multiplexer.blockchain.connected:
787+
raise Exception("Libbitcoin server not online")
788+
with open(DATA_FOLDER + "cases/" + order_id + ".json", "r") as filename:
789+
contract = json.load(filename, object_pairs_hook=OrderedDict)
791790

792-
if "vendor_order_confirmation" in contract and float(vendor_percentage) > 0:
793-
vendor_address = contract["vendor_order_confirmation"]["invoice"]["payout"]["address"]
794-
795-
buyer_address = contract["buyer_order"]["order"]["refund_address"]
796-
797-
buyer_guid = contract["buyer_order"]["order"]["id"]["guid"]
798-
buyer_enc_key = nacl.signing.VerifyKey(
799-
contract["buyer_order"]["order"]["id"]["pubkeys"]["guid"],
800-
encoder=nacl.encoding.HexEncoder).to_curve25519_public_key()
801-
vendor_guid = contract["vendor_offer"]["listing"]["id"]["guid"]
802-
vendor_enc_key = nacl.signing.VerifyKey(
803-
contract["vendor_offer"]["listing"]["id"]["pubkeys"]["guid"],
804-
encoder=nacl.encoding.HexEncoder).to_curve25519_public_key()
805-
806-
payment_address = contract["buyer_order"]["order"]["payment"]["address"]
807-
808-
def history_fetched(ec, history):
809-
outpoints = []
810-
satoshis = 0
811-
outputs = []
812-
dispute_json = {"dispute_resolution": {"resolution": {}}}
813-
if ec:
814-
print ec
815-
else:
816-
for tx_type, txid, i, height, value in history: # pylint: disable=W0612
817-
if tx_type == obelisk.PointIdent.Output:
818-
satoshis += value
819-
o = {
820-
"txid": txid.encode("hex"),
821-
"vout": i,
822-
"value": value,
823-
"scriptPubKey": "00"
824-
}
825-
if o not in outpoints:
826-
outpoints.append(o)
827-
828-
satoshis -= TRANSACTION_FEE
829-
moderator_fee = int(float(moderator_percentage) * satoshis)
830-
satoshis -= moderator_fee
831-
832-
outputs.append({'value': moderator_fee, 'address': moderator_address})
833-
dispute_json["dispute_resolution"]["resolution"]["moderator_address"] = moderator_address
834-
dispute_json["dispute_resolution"]["resolution"]["moderator_fee"] = moderator_fee
835-
dispute_json["dispute_resolution"]["resolution"]["transaction_fee"] = TRANSACTION_FEE
836-
if float(buyer_percentage) > 0:
837-
amt = int(float(buyer_percentage) * satoshis)
838-
dispute_json["dispute_resolution"]["resolution"]["buyer_payout"] = amt
839-
outputs.append({'value': amt,
840-
'address': buyer_address})
841-
if float(vendor_percentage) > 0:
842-
amt = int(float(vendor_percentage) * satoshis)
843-
dispute_json["dispute_resolution"]["resolution"]["vendor_payout"] = amt
844-
outputs.append({'value': amt,
845-
'address': vendor_address})
846-
847-
tx = BitcoinTransaction.make_unsigned(outpoints, outputs,
848-
testnet=self.protocol.multiplexer.testnet)
849-
chaincode = contract["buyer_order"]["order"]["payment"]["chaincode"]
850-
redeem_script = str(contract["buyer_order"]["order"]["payment"]["redeem_script"])
851-
masterkey_m = bitcointools.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey)
852-
moderator_priv = derive_childkey(masterkey_m, chaincode, bitcointools.MAINNET_PRIVATE)
853-
854-
signatures = tx.create_signature(moderator_priv, redeem_script)
855-
dispute_json["dispute_resolution"]["resolution"]["order_id"] = order_id
856-
dispute_json["dispute_resolution"]["resolution"]["tx_signatures"] = signatures
857-
dispute_json["dispute_resolution"]["resolution"]["claim"] = self.db.cases.get_claim(order_id)
858-
dispute_json["dispute_resolution"]["resolution"]["decision"] = resolution
859-
dispute_json["dispute_resolution"]["signature"] = \
860-
base64.b64encode(KeyChain(self.db).signing_key.sign(json.dumps(
861-
dispute_json["dispute_resolution"]["resolution"], indent=4))[:64])
862-
863-
def get_node(node_to_ask, recipient_guid, public_key):
864-
def parse_response(response):
865-
if not response[0]:
866-
self.send_message(Node(unhexlify(recipient_guid)),
867-
public_key.encode(),
868-
objects.PlaintextMessage.Type.Value("DISPUTE_CLOSE"),
869-
dispute_json,
870-
order_id,
871-
store_only=True)
872-
873-
if node_to_ask:
874-
skephem = PrivateKey.generate()
875-
pkephem = skephem.public_key.encode(nacl.encoding.RawEncoder)
876-
box = Box(skephem, public_key)
877-
nonce = nacl.utils.random(Box.NONCE_SIZE)
878-
ciphertext = box.encrypt(json.dumps(dispute_json, indent=4), nonce)
879-
d = self.protocol.callDisputeClose(node_to_ask, pkephem, ciphertext)
880-
return d.addCallback(parse_response)
881-
else:
882-
return parse_response([False])
883-
884-
self.kserver.resolve(unhexlify(vendor_guid)).addCallback(get_node, vendor_guid, vendor_enc_key)
885-
self.kserver.resolve(unhexlify(buyer_guid)).addCallback(get_node, buyer_guid, buyer_enc_key)
886-
self.db.cases.update_status(order_id, 1)
887-
888-
# TODO: add a timeout on this call
889-
self.protocol.multiplexer.blockchain.fetch_history2(payment_address, history_fetched)
890-
except Exception:
891-
pass
791+
if "vendor_order_confirmation" in contract and float(vendor_percentage) > 0:
792+
vendor_address = contract["vendor_order_confirmation"]["invoice"]["payout"]["address"]
793+
elif "vendor_order_confirmation" not in contract and float(vendor_percentage) > 0:
794+
raise Exception("Cannot refund seller before order confirmation is sent")
795+
796+
buyer_address = contract["buyer_order"]["order"]["refund_address"]
797+
798+
buyer_guid = contract["buyer_order"]["order"]["id"]["guid"]
799+
buyer_enc_key = nacl.signing.VerifyKey(
800+
contract["buyer_order"]["order"]["id"]["pubkeys"]["guid"],
801+
encoder=nacl.encoding.HexEncoder).to_curve25519_public_key()
802+
vendor_guid = contract["vendor_offer"]["listing"]["id"]["guid"]
803+
vendor_enc_key = nacl.signing.VerifyKey(
804+
contract["vendor_offer"]["listing"]["id"]["pubkeys"]["guid"],
805+
encoder=nacl.encoding.HexEncoder).to_curve25519_public_key()
806+
807+
payment_address = contract["buyer_order"]["order"]["payment"]["address"]
808+
809+
def history_fetched(ec, history):
810+
outpoints = []
811+
satoshis = 0
812+
outputs = []
813+
dispute_json = {"dispute_resolution": {"resolution": {}}}
814+
timeout.cancel()
815+
if ec:
816+
print ec
817+
else:
818+
for tx_type, txid, i, height, value in history: # pylint: disable=W0612
819+
if tx_type == obelisk.PointIdent.Output:
820+
satoshis += value
821+
o = {
822+
"txid": txid.encode("hex"),
823+
"vout": i,
824+
"value": value,
825+
"scriptPubKey": "00"
826+
}
827+
if o not in outpoints:
828+
outpoints.append(o)
829+
830+
satoshis -= TRANSACTION_FEE
831+
moderator_fee = int(float(moderator_percentage) * satoshis)
832+
satoshis -= moderator_fee
833+
834+
outputs.append({'value': moderator_fee, 'address': moderator_address})
835+
dispute_json["dispute_resolution"]["resolution"]["moderator_address"] = moderator_address
836+
dispute_json["dispute_resolution"]["resolution"]["moderator_fee"] = moderator_fee
837+
dispute_json["dispute_resolution"]["resolution"]["transaction_fee"] = TRANSACTION_FEE
838+
if float(buyer_percentage) > 0:
839+
amt = int(float(buyer_percentage) * satoshis)
840+
dispute_json["dispute_resolution"]["resolution"]["buyer_payout"] = amt
841+
outputs.append({'value': amt,
842+
'address': buyer_address})
843+
if float(vendor_percentage) > 0:
844+
amt = int(float(vendor_percentage) * satoshis)
845+
dispute_json["dispute_resolution"]["resolution"]["vendor_payout"] = amt
846+
outputs.append({'value': amt,
847+
'address': vendor_address})
848+
849+
tx = BitcoinTransaction.make_unsigned(outpoints, outputs,
850+
testnet=self.protocol.multiplexer.testnet)
851+
chaincode = contract["buyer_order"]["order"]["payment"]["chaincode"]
852+
redeem_script = str(contract["buyer_order"]["order"]["payment"]["redeem_script"])
853+
masterkey_m = bitcointools.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey)
854+
moderator_priv = derive_childkey(masterkey_m, chaincode, bitcointools.MAINNET_PRIVATE)
855+
856+
signatures = tx.create_signature(moderator_priv, redeem_script)
857+
dispute_json["dispute_resolution"]["resolution"]["order_id"] = order_id
858+
dispute_json["dispute_resolution"]["resolution"]["tx_signatures"] = signatures
859+
dispute_json["dispute_resolution"]["resolution"]["claim"] = self.db.cases.get_claim(order_id)
860+
dispute_json["dispute_resolution"]["resolution"]["decision"] = resolution
861+
dispute_json["dispute_resolution"]["signature"] = \
862+
base64.b64encode(KeyChain(self.db).signing_key.sign(json.dumps(
863+
dispute_json["dispute_resolution"]["resolution"], indent=4))[:64])
864+
865+
def get_node(node_to_ask, recipient_guid, public_key):
866+
def parse_response(response):
867+
if not response[0]:
868+
self.send_message(Node(unhexlify(recipient_guid)),
869+
public_key.encode(),
870+
objects.PlaintextMessage.Type.Value("DISPUTE_CLOSE"),
871+
dispute_json,
872+
order_id,
873+
store_only=True)
874+
875+
if node_to_ask:
876+
skephem = PrivateKey.generate()
877+
pkephem = skephem.public_key.encode(nacl.encoding.RawEncoder)
878+
box = Box(skephem, public_key)
879+
nonce = nacl.utils.random(Box.NONCE_SIZE)
880+
ciphertext = box.encrypt(json.dumps(dispute_json, indent=4), nonce)
881+
self.protocol.callDisputeClose(node_to_ask, pkephem, ciphertext).addCallback(parse_response)
882+
else:
883+
parse_response([False])
884+
885+
self.kserver.resolve(unhexlify(vendor_guid)).addCallback(get_node, vendor_guid, vendor_enc_key)
886+
self.kserver.resolve(unhexlify(buyer_guid)).addCallback(get_node, buyer_guid, buyer_enc_key)
887+
self.db.cases.update_status(order_id, 1)
888+
d.callback(True)
889+
890+
d = defer.Deferred()
891+
892+
def libbitcoin_timeout():
893+
d.callback("timed out")
894+
895+
timeout = reactor.callLater(5, libbitcoin_timeout)
896+
self.protocol.multiplexer.blockchain.fetch_history2(payment_address, history_fetched)
897+
return d
892898

893899
def release_funds(self, order_id):
894900
"""
@@ -991,6 +997,65 @@ def get_result(result):
991997
d = self.protocol.callGetRatings(node_to_ask, listing_hash)
992998
return d.addCallback(get_result)
993999

1000+
def refund(self, order_id, reason):
1001+
"""
1002+
Refund the given order_id. If this is a direct payment he transaction will be
1003+
immediately broadcast to the Bitcoin network otherwise the refund message sent
1004+
to the buyer with contain the signature.
1005+
"""
1006+
file_path = DATA_FOLDER + "store/contracts/in progress/" + order_id + ".json"
1007+
outpoints = pickle.loads(self.db.sales.get_outpoint(order_id))
1008+
1009+
with open(file_path, 'r') as filename:
1010+
contract = json.load(filename, object_pairs_hook=OrderedDict)
1011+
1012+
buyer_guid = contract["buyer_order"]["order"]["id"]["guid"]
1013+
buyer_enc_key = nacl.signing.VerifyKey(
1014+
contract["buyer_order"]["order"]["id"]["pubkeys"]["guid"],
1015+
encoder=nacl.encoding.HexEncoder).to_curve25519_public_key()
1016+
refund_address = contract["buyer_order"]["order"]["refund_address"]
1017+
redeem_script = contract["buyer_order"]["order"]["payment"]["redeem_script"]
1018+
tx = BitcoinTransaction.make_unsigned(outpoints, refund_address,
1019+
testnet=self.protocol.multiplexer.testnet)
1020+
chaincode = contract["buyer_order"]["order"]["payment"]["chaincode"]
1021+
masterkey_v = bitcointools.bip32_extract_key(KeyChain(self.db).bitcoin_master_privkey)
1022+
vendor_priv = derive_childkey(masterkey_v, chaincode, bitcointools.MAINNET_PRIVATE)
1023+
1024+
refund_json = {"refund": {}}
1025+
refund_json["refund"]["order_id"] = order_id
1026+
refund_json["refund"]["reason"] = reason
1027+
if "moderator" in contract["buyer_order"]["order"]:
1028+
sigs = tx.create_signature(vendor_priv, redeem_script)
1029+
refund_json["refund"]["value"] = tx.get_out_value()
1030+
refund_json["refund"]["signature(s)"] = sigs
1031+
else:
1032+
tx.sign(vendor_priv)
1033+
tx.broadcast(self.protocol.multiplexer.blockchain)
1034+
refund_json["refund"]["txid"] = tx.get_hash()
1035+
1036+
def get_node(node_to_ask):
1037+
def parse_response(response):
1038+
if not response[0]:
1039+
self.send_message(Node(unhexlify(buyer_guid)),
1040+
buyer_enc_key.encode(),
1041+
objects.PlaintextMessage.Type.Value("REFUND"),
1042+
refund_json,
1043+
order_id,
1044+
store_only=True)
1045+
1046+
if node_to_ask:
1047+
skephem = PrivateKey.generate()
1048+
pkephem = skephem.public_key.encode(nacl.encoding.RawEncoder)
1049+
box = Box(skephem, buyer_enc_key)
1050+
nonce = nacl.utils.random(Box.NONCE_SIZE)
1051+
ciphertext = box.encrypt(json.dumps(refund_json, indent=4), nonce)
1052+
d = self.protocol.callRefund(node_to_ask, pkephem, ciphertext)
1053+
return d.addCallback(parse_response)
1054+
else:
1055+
return parse_response([False])
1056+
1057+
self.log.info("sending refund message to %s" % buyer_guid)
1058+
return self.kserver.resolve(unhexlify(buyer_guid)).addCallback(get_node)
9941059

9951060
@staticmethod
9961061
def cache(file_to_save, filename):

protos/objects.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,6 @@ message PlaintextMessage {
164164
DISPUTE_CLOSE = 3;
165165
ORDER_CONFIRMATION = 4;
166166
RECEIPT = 5;
167+
REFUND = 6;
167168
}
168169
}

0 commit comments

Comments
 (0)