@@ -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 ):
0 commit comments