Exemplo n.º 1
0
 def on_tx_received(self, nick, txhex, offerinfo):
     try:
         tx = btc.deserialize(txhex)
     except IndexError as e:
         return (False, 'malformed txhex. ' + repr(e))
     jlog.info('obtained tx\n' + pprint.pformat(tx))
     goodtx, errmsg = self.verify_unsigned_tx(tx, offerinfo)
     if not goodtx:
         jlog.info('not a good tx, reason=' + errmsg)
         return (False, errmsg)
     jlog.info('goodtx')
     sigs = []
     utxos = offerinfo["utxos"]
     for index, ins in enumerate(tx['ins']):
         utxo = ins['outpoint']['hash'] + ':' + str(
             ins['outpoint']['index'])
         if utxo not in utxos.keys():
             continue
         addr = utxos[utxo]['address']
         amount = utxos[utxo]["value"]
         txs = self.wallet.sign(txhex,
                                index,
                                self.wallet.get_key_from_addr(addr),
                                amount=amount)
         sigmsg = btc.deserialize(txs)["ins"][index]["script"].decode("hex")
         if "txinwitness" in btc.deserialize(txs)["ins"][index].keys():
             #We prepend the witness data since we want (sig, pub, scriptCode);
             #also, the items in witness are not serialize_script-ed.
             sigmsg = "".join([
                 btc.serialize_script_unit(x.decode("hex"))
                 for x in btc.deserialize(txs)["ins"][index]["txinwitness"]
             ]) + sigmsg
         sigs.append(base64.b64encode(sigmsg))
     return (True, sigs)
Exemplo n.º 2
0
    def recv_tx(self, nick, txhex):
        try:
            self.tx = btc.deserialize(txhex)
        except IndexError as e:
            self.maker.msgchan.send_error(nick, 'malformed txhex. ' + repr(e))
        log.debug('obtained tx\n' + pprint.pformat(self.tx))
        goodtx, errmsg = self.verify_unsigned_tx(self.tx)
        if not goodtx:
            log.debug('not a good tx, reason=' + errmsg)
            self.maker.msgchan.send_error(nick, errmsg)
        # TODO: the above 3 errors should be encrypted, but it's a bit messy.
        log.debug('goodtx')
        sigs = []
        for index, ins in enumerate(self.tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index'])
            if utxo not in self.utxos:
                continue
            addr = self.utxos[utxo]['address']
            txs = btc.sign(txhex, index,
                           self.maker.wallet.get_key_from_addr(addr))
            sigs.append(base64.b64encode(btc.deserialize(txs)['ins'][index][
                                             'script'].decode('hex')))
        # len(sigs) > 0 guarenteed since i did verify_unsigned_tx()

        jm_single().bc_interface.add_tx_notify(
                self.tx, self.unconfirm_callback,
                self.confirm_callback, self.cj_addr)
        log.debug('sending sigs ' + str(sigs))
        self.maker.msgchan.send_sigs(nick, sigs)
        self.maker.active_orders[nick] = None
Exemplo n.º 3
0
    def recv_tx(self, nick, txhex):
        try:
            self.tx = btc.deserialize(txhex)
        except IndexError as e:
            self.maker.msgchan.send_error(nick, 'malformed txhex. ' + repr(e))
        log.debug('obtained tx\n' + pprint.pformat(self.tx))
        goodtx, errmsg = self.verify_unsigned_tx(self.tx)
        if not goodtx:
            log.debug('not a good tx, reason=' + errmsg)
            self.maker.msgchan.send_error(nick, errmsg)
        # TODO: the above 3 errors should be encrypted, but it's a bit messy.
        log.debug('goodtx')
        sigs = []
        for index, ins in enumerate(self.tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in self.utxos:
                continue
            addr = self.utxos[utxo]['address']
            txs = btc.sign(txhex, index,
                           self.maker.wallet.get_key_from_addr(addr))
            sigs.append(
                base64.b64encode(
                    btc.deserialize(txs)['ins'][index]['script'].decode(
                        'hex')))
        # len(sigs) > 0 guarenteed since i did verify_unsigned_tx()

        jm_single().bc_interface.add_tx_notify(self.tx,
                                               self.unconfirm_callback,
                                               self.confirm_callback,
                                               self.cj_addr)
        log.debug('sending sigs ' + str(sigs))
        self.maker.msgchan.send_sigs(nick, sigs)
        self.maker.active_orders[nick] = None
Exemplo n.º 4
0
def sign_donation_tx(tx, i, priv):
    from bitcoin.main import fast_multiply, decode_privkey, G, inv, N
    from bitcoin.transaction import der_encode_sig
    k = sign_k
    hashcode = btc.SIGHASH_ALL
    i = int(i)
    if len(priv) <= 33:
        priv = btc.safe_hexlify(priv)
    pub = btc.privkey_to_pubkey(priv)
    address = btc.pubkey_to_address(pub)
    signing_tx = btc.signature_form(
            tx, i, btc.mk_pubkey_script(address), hashcode)

    msghash = btc.bin_txhash(signing_tx, hashcode)
    z = btc.hash_to_int(msghash)
    # k = deterministic_generate_k(msghash, priv)
    r, y = fast_multiply(G, k)
    s = inv(k, N) * (z + r * decode_privkey(priv)) % N
    rawsig = 27 + (y % 2), r, s

    sig = der_encode_sig(*rawsig) + btc.encode(hashcode, 16, 2)
    # sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
    txobj = btc.deserialize(tx)
    txobj["ins"][i]["script"] = btc.serialize_script([sig, pub])
    return btc.serialize(txobj)
    def self_sign(self):
        # now sign it ourselves
        our_inputs = {}
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            script = self.wallet.addr_to_script(self.input_utxos[utxo]['address'])
            amount = self.input_utxos[utxo]['value']
            our_inputs[index] = (script, amount)

        # FIXME: ugly hack
        tx_bin = btc.deserialize(unhexlify(btc.serialize(self.latest_tx)))
        self.wallet.sign_tx(tx_bin, our_inputs)

        self.latest_tx = btc.deserialize(hexlify(btc.serialize(tx_bin)))
Exemplo n.º 6
0
 def self_sign(self):
     # now sign it ourselves
     tx = btc.serialize(self.latest_tx)
     if self.sign_method == "wallet":
         #Currently passes addresses of to-be-signed inputs
         #to backend wallet; this is correct for Electrum, may need
         #different info for other backends.
         addrs = {}
         for index, ins in enumerate(self.latest_tx['ins']):
             utxo = ins['outpoint']['hash'] + ':' + str(
                 ins['outpoint']['index'])
             if utxo not in self.input_utxos.keys():
                 continue
             addrs[index] = self.input_utxos[utxo]['address']
         tx = self.wallet.sign_tx(tx, addrs)
     else:
         for index, ins in enumerate(self.latest_tx['ins']):
             utxo = ins['outpoint']['hash'] + ':' + str(
                 ins['outpoint']['index'])
             if utxo not in self.input_utxos.keys():
                 continue
             addr = self.input_utxos[utxo]['address']
             tx = self.sign_tx(tx, index,
                               self.wallet.get_key_from_addr(addr))
     self.latest_tx = btc.deserialize(tx)
Exemplo n.º 7
0
def sign_donation_tx(tx, i, priv):
    from bitcoin.main import fast_multiply, decode_privkey, G, inv, N
    from bitcoin.transaction import der_encode_sig
    k = sign_k
    hashcode = btc.SIGHASH_ALL
    i = int(i)
    if len(priv) <= 33:
        priv = btc.safe_hexlify(priv)
    pub = btc.privkey_to_pubkey(priv)
    address = btc.pubkey_to_address(pub)
    signing_tx = btc.signature_form(tx, i, btc.mk_pubkey_script(address),
                                    hashcode)

    msghash = btc.bin_txhash(signing_tx, hashcode)
    z = btc.hash_to_int(msghash)
    # k = deterministic_generate_k(msghash, priv)
    r, y = fast_multiply(G, k)
    s = inv(k, N) * (z + r * decode_privkey(priv)) % N
    rawsig = 27 + (y % 2), r, s

    sig = der_encode_sig(*rawsig) + btc.encode(hashcode, 16, 2)
    # sig = ecdsa_tx_sign(signing_tx, priv, hashcode)
    txobj = btc.deserialize(tx)
    txobj["ins"][i]["script"] = btc.serialize_script([sig, pub])
    return btc.serialize(txobj)
Exemplo n.º 8
0
 def get_deser_from_gettransaction(self, rpcretval):
     """Get full transaction deserialization from a call
     to `gettransaction`
     """
     if not "hex" in rpcretval:
         log.info("Malformed gettransaction output")
         return None
     #str cast for unicode
     hexval = str(rpcretval["hex"])
     return btc.deserialize(hexval)
Exemplo n.º 9
0
    def on_tx_received(self, nick, txhex, offerinfo):
        """Called when the counterparty has sent an unsigned
        transaction. Sigs are created and returned if and only
        if the transaction passes verification checks (see
        verify_unsigned_tx()).
        """
        try:
            tx = btc.deserialize(txhex)
        except (IndexError, SerializationError,
                SerializationTruncationError) as e:
            return (False, 'malformed txhex. ' + repr(e))
        jlog.info('obtained tx\n' + pprint.pformat(tx))
        goodtx, errmsg = self.verify_unsigned_tx(tx, offerinfo)
        if not goodtx:
            jlog.info('not a good tx, reason=' + errmsg)
            return (False, errmsg)
        jlog.info('goodtx')
        sigs = []
        utxos = offerinfo["utxos"]

        our_inputs = {}
        for index, ins in enumerate(tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in utxos:
                continue
            script = self.wallet.addr_to_script(utxos[utxo]['address'])
            amount = utxos[utxo]['value']
            our_inputs[index] = (script, amount)

        txs = self.wallet.sign_tx(btc.deserialize(unhexlify(txhex)),
                                  our_inputs)

        for index in our_inputs:
            sigmsg = txs['ins'][index]['script']
            if 'txinwitness' in txs['ins'][index]:
                #We prepend the witness data since we want (sig, pub, scriptCode);
                #also, the items in witness are not serialize_script-ed.
                sigmsg = b''.join(
                    btc.serialize_script_unit(x)
                    for x in txs['ins'][index]['txinwitness']) + sigmsg
            sigs.append(base64.b64encode(sigmsg))
        return (True, sigs)
Exemplo n.º 10
0
 def self_sign(self):
     # now sign it ourselves
     tx = btc.serialize(self.latest_tx)
     if isinstance(jm_single().bc_interface, ElectrumWalletInterface):
         #can defer signing to our wallet; it will handle
         #all relevant inputs
         tx = self.wallet.sign_tx(tx)
         log.debug("Back in self_sign in cjtx, tx is: " + str(tx))
     else:
         for index, ins in enumerate(self.latest_tx['ins']):
             utxo = ins['outpoint']['hash'] + ':' + str(
                     ins['outpoint']['index'])
             if utxo not in self.input_utxos.keys():
                 continue
             addr = self.input_utxos[utxo]['address']
             tx = self.sign_tx(tx, index, self.wallet.get_key_from_addr(addr))
     self.latest_tx = btc.deserialize(tx)
Exemplo n.º 11
0
 def self_sign(self):
     # now sign it ourselves
     tx = btc.serialize(self.latest_tx)
     if isinstance(jm_single().bc_interface, ElectrumWalletInterface):
         #can defer signing to our wallet; it will handle
         #all relevant inputs
         tx = self.wallet.sign_tx(tx)
         log.debug("Back in self_sign in cjtx, tx is: " + str(tx))
     else:
         for index, ins in enumerate(self.latest_tx['ins']):
             utxo = ins['outpoint']['hash'] + ':' + str(
                 ins['outpoint']['index'])
             if utxo not in self.input_utxos.keys():
                 continue
             addr = self.input_utxos[utxo]['address']
             tx = self.sign_tx(tx, index,
                               self.wallet.get_key_from_addr(addr))
     self.latest_tx = btc.deserialize(tx)
Exemplo n.º 12
0
 def on_JM_TX_RECEIVED(self, nick, txhex, offer):
     offer = _byteify(json.loads(offer))
     retval = self.client.on_tx_received(nick, txhex, offer)
     if not retval[0]:
         jlog.info("Maker refuses to continue on receipt of tx")
     else:
         sigs = retval[1]
         self.finalized_offers[nick] = offer
         tx = btc.deserialize(txhex)
         self.finalized_offers[nick]["txd"] = tx
         jm_single().bc_interface.add_tx_notify(tx, self.unconfirm_callback,
                 self.confirm_callback, offer["cjaddr"],
                 wallet_name=jm_single().bc_interface.get_wallet_name(
                     self.client.wallet),
                 txid_flag=False,
                 vb=get_p2sh_vbyte())
         d = self.callRemote(commands.JMTXSigs,
                             nick=nick,
                             sigs=json.dumps(sigs))
         self.defaultCallbacks(d)
     return {"accepted": True}
Exemplo n.º 13
0
    def outputs_watcher(self, wallet_name, notifyaddr, tx_output_set,
                        unconfirmfun, confirmfun, timeoutfun):
        """Given a key for the watcher loop (notifyaddr), a wallet name (account),
        a set of outputs, and unconfirm, confirm and timeout callbacks,
        check to see if a transaction matching that output set has appeared in
        the wallet. Call the callbacks and update the watcher loop state.
        End the loop when the confirmation has been seen (no spent monitoring here).
        """
        wl = self.tx_watcher_loops[notifyaddr]
        print('txoutset=' + pprint.pformat(tx_output_set))
        unconftx = self.get_from_electrum('blockchain.address.get_mempool',
                                          notifyaddr,
                                          blocking=True).get('result')
        unconftxs = set([str(t['tx_hash']) for t in unconftx])
        if len(unconftxs):
            txdatas = []
            for txid in unconftxs:
                txdatas.append({
                    'id':
                    txid,
                    'hex':
                    str(
                        self.get_from_electrum('blockchain.transaction.get',
                                               txid,
                                               blocking=True).get('result'))
                })
            unconfirmed_txid = None
            for txdata in txdatas:
                txhex = txdata['hex']
                outs = set([(sv['script'], sv['value'])
                            for sv in btc.deserialize(txhex)['outs']])
                print('unconfirm query outs = ' + str(outs))
                if outs == tx_output_set:
                    unconfirmed_txid = txdata['id']
                    unconfirmed_txhex = txhex
                    break
            #call unconf callback if it was found in the mempool
            if unconfirmed_txid and not wl[1]:
                print("Tx: " + str(unconfirmed_txid) + " seen on network.")
                unconfirmfun(btc.deserialize(unconfirmed_txhex),
                             unconfirmed_txid)
                wl[1] = True
                return

        conftx = self.get_from_electrum('blockchain.address.listunspent',
                                        notifyaddr,
                                        blocking=True).get('result')
        conftxs = set([str(t['tx_hash']) for t in conftx])
        if len(conftxs):
            txdatas = []
            for txid in conftxs:
                txdata = str(
                    self.get_from_electrum('blockchain.transaction.get',
                                           txid,
                                           blocking=True).get('result'))
                txdatas.append({'hex': txdata, 'id': txid})
            confirmed_txid = None
            for txdata in txdatas:
                txhex = txdata['hex']
                outs = set([(sv['script'], sv['value'])
                            for sv in btc.deserialize(txhex)['outs']])
                print('confirm query outs = ' + str(outs))
                if outs == tx_output_set:
                    confirmed_txid = txdata['id']
                    confirmed_txhex = txhex
                    break
            if confirmed_txid and not wl[2]:
                confirmfun(btc.deserialize(confirmed_txhex), confirmed_txid, 1)
                wl[2] = True
                wl[0].stop()
                return
Exemplo n.º 14
0
    def do_HEAD(self):
        pages = ('/walletnotify?', '/alertnotify?')

        if self.path.startswith('/walletnotify?'):
            txid = self.path[len(pages[0]):]
            if not re.match('^[0-9a-fA-F]*$', txid):
                log.debug('not a txid')
                return
            try:
                tx = self.btcinterface.rpc('getrawtransaction', [txid])
            except (JsonRpcError, JsonRpcConnectionError) as e:
                log.debug('transaction not found, probably a conflict')
                return
            if not re.match('^[0-9a-fA-F]*$', tx):
                log.debug('not a txhex')
                return
            txd = btc.deserialize(tx)
            tx_output_set = set([(sv['script'], sv['value']) for sv in txd[
                'outs']])

            txnotify_tuple = None
            unconfirmfun, confirmfun, timeoutfun, uc_called = (None, None,
                None, None)
            for tnf in self.btcinterface.txnotify_fun:
                tx_out = tnf[0]
                if tx_out == tx_output_set:
                    txnotify_tuple = tnf
                    tx_out, unconfirmfun, confirmfun, timeoutfun, uc_called = tnf
                    break
            if unconfirmfun is None:
                log.debug('txid=' + txid + ' not being listened for')
            else:
                # on rare occasions people spend their output without waiting
                #  for a confirm
                txdata = None
                for n in range(len(txd['outs'])):
                    txdata = self.btcinterface.rpc('gettxout', [txid, n, True])
                    if txdata is not None:
                        break
                assert txdata is not None
                if txdata['confirmations'] == 0:
                    unconfirmfun(txd, txid)
                    # TODO pass the total transfered amount value here somehow
                    # wallet_name = self.get_wallet_name()
                    # amount =
                    # bitcoin-cli move wallet_name "" amount
                    self.btcinterface.txnotify_fun.remove(txnotify_tuple)
                    self.btcinterface.txnotify_fun.append(txnotify_tuple[:-1]
                        + (True,))
                    log.debug('ran unconfirmfun')
                    if timeoutfun:
                        threading.Timer(jm_single().config.getfloat('TIMEOUT',
                            'confirm_timeout_hours')*60*60,
                            bitcoincore_timeout_callback,
                            args=(True, tx_output_set,
                            self.btcinterface.txnotify_fun,
                            timeoutfun)).start()
                else:
                    if not uc_called:
                        unconfirmfun(txd, txid)
                        log.debug('saw confirmed tx before unconfirmed, ' +
                            'running unconfirmfun first')
                    confirmfun(txd, txid, txdata['confirmations'])
                    self.btcinterface.txnotify_fun.remove(txnotify_tuple)
                    log.debug('ran confirmfun')

        elif self.path.startswith('/alertnotify?'):
            jm_single().core_alert[0] = urllib.unquote(self.path[len(pages[1]):])
            log.debug('Got an alert!\nMessage=' + jm_single().core_alert[0])

        else:
            log.debug('ERROR: This is not a handled URL path.  You may want to check your notify URL for typos.')

        request = urllib2.Request('http://localhost:' + str(self.base_server.server_address[1] + 1) + self.path)
        request.get_method = lambda : 'HEAD'
        try:
            urllib2.urlopen(request)
        except urllib2.URLError:
            pass
        self.send_response(200)
        # self.send_header('Connection', 'close')
        self.end_headers()
Exemplo n.º 15
0
    def receive_utxos(self, ioauth_data):
        """Triggered when the daemon returns utxo data from
        makers who responded; this is the completion of phase 1
        of the protocol
        """
        if self.aborted:
            return (False, "User aborted")
        rejected_counterparties = []
        #Enough data, but need to authorize against the btc pubkey first.
        for nick, nickdata in ioauth_data.iteritems():
            utxo_list, auth_pub, cj_addr, change_addr, btc_sig, maker_pk = nickdata
            if not self.auth_counterparty(btc_sig, auth_pub, maker_pk):
                jlog.debug(
                    "Counterparty encryption verification failed, aborting")
                #This counterparty must be rejected
                rejected_counterparties.append(nick)

        for rc in rejected_counterparties:
            del ioauth_data[rc]

        self.maker_utxo_data = {}

        for nick, nickdata in ioauth_data.iteritems():
            utxo_list, auth_pub, cj_addr, change_addr, btc_sig, maker_pk = nickdata
            self.utxos[nick] = utxo_list
            utxo_data = jm_single().bc_interface.query_utxo_set(
                self.utxos[nick])
            if None in utxo_data:
                jlog.debug(('ERROR outputs unconfirmed or already spent. '
                            'utxo_data={}').format(pprint.pformat(utxo_data)))
                # when internal reviewing of makers is created, add it here to
                # immediately quit; currently, the timeout thread suffices.
                continue

            #Complete maker authorization:
            #Extract the address fields from the utxos
            #Construct the Bitcoin address for the auth_pub field
            #Ensure that at least one address from utxos corresponds.
            input_addresses = [d['address'] for d in utxo_data]
            auth_address = btc.pubkey_to_address(auth_pub, get_p2pk_vbyte())
            if not auth_address in input_addresses:
                jlog.warn("ERROR maker's (" + nick + ")"
                          " authorising pubkey is not included "
                          "in the transaction: " + str(auth_address))
                #this will not be added to the transaction, so we will have
                #to recheck if we have enough
                continue
            total_input = sum([d['value'] for d in utxo_data])
            real_cjfee = calc_cj_fee(self.orderbook[nick]['ordertype'],
                                     self.orderbook[nick]['cjfee'],
                                     self.cjamount)
            change_amount = (total_input - self.cjamount -
                             self.orderbook[nick]['txfee'] + real_cjfee)

            # certain malicious and/or incompetent liquidity providers send
            # inputs totalling less than the coinjoin amount! this leads to
            # a change output of zero satoshis; this counterparty must be removed.
            if change_amount < jm_single().DUST_THRESHOLD:
                fmt = ('ERROR counterparty requires sub-dust change. nick={}'
                       'totalin={:d} cjamount={:d} change={:d}').format
                jlog.debug(fmt(nick, total_input, self.cjamount,
                               change_amount))
                jlog.warn("Invalid change, too small, nick= " + nick)
                continue

            self.outputs.append({
                'address': change_addr,
                'value': change_amount
            })
            fmt = ('fee breakdown for {} totalin={:d} '
                   'cjamount={:d} txfee={:d} realcjfee={:d}').format
            jlog.debug(
                fmt(nick, total_input, self.cjamount,
                    self.orderbook[nick]['txfee'], real_cjfee))
            self.outputs.append({'address': cj_addr, 'value': self.cjamount})
            self.cjfee_total += real_cjfee
            self.maker_txfee_contributions += self.orderbook[nick]['txfee']
            self.maker_utxo_data[nick] = utxo_data

        #Apply business logic of how many counterparties are enough:
        if len(self.maker_utxo_data.keys()) < jm_single().config.getint(
                "POLICY", "minimum_makers"):
            self.taker_info_callback("INFO",
                                     "Not enough counterparties, aborting.")
            return (False,
                    "Not enough counterparties responded to fill, giving up")

        self.taker_info_callback("INFO", "Got all parts, enough to build a tx")
        self.nonrespondants = list(self.maker_utxo_data.keys())

        my_total_in = sum(
            [va['value'] for u, va in self.input_utxos.iteritems()])
        if self.my_change_addr:
            #Estimate fee per choice of next/3/6 blocks targetting.
            estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])),
                                            len(self.outputs) + 2)
            jlog.info("Based on initial guess: " + str(self.total_txfee) +
                      ", we estimated a miner fee of: " + str(estimated_fee))
            #reset total
            self.total_txfee = estimated_fee
        my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0)
        my_change_value = (my_total_in - self.cjamount - self.cjfee_total -
                           my_txfee)
        #Since we could not predict the maker's inputs, we may end up needing
        #too much such that the change value is negative or small. Note that
        #we have tried to avoid this based on over-estimating the needed amount
        #in SendPayment.create_tx(), but it is still a possibility if one maker
        #uses a *lot* of inputs.
        if self.my_change_addr and my_change_value <= 0:
            raise ValueError("Calculated transaction fee of: " +
                             str(self.total_txfee) +
                             " is too large for our inputs;Please try again.")
        elif self.my_change_addr and my_change_value <= jm_single(
        ).BITCOIN_DUST_THRESHOLD:
            jlog.info("Dynamically calculated change lower than dust: " +
                      str(my_change_value) + "; dropping.")
            self.my_change_addr = None
            my_change_value = 0
        jlog.info(
            'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d'
            % (my_total_in, my_txfee, self.maker_txfee_contributions,
               self.cjfee_total, my_change_value))
        if self.my_change_addr is None:
            if my_change_value != 0 and abs(my_change_value) != 1:
                # seems you wont always get exactly zero because of integer
                # rounding so 1 satoshi extra or fewer being spent as miner
                # fees is acceptable
                jlog.debug(('WARNING CHANGE NOT BEING '
                            'USED\nCHANGEVALUE = {}').format(my_change_value))
        else:
            self.outputs.append({
                'address': self.my_change_addr,
                'value': my_change_value
            })
        self.utxo_tx = [
            dict([('output', u)]) for u in sum(self.utxos.values(), [])
        ]
        self.outputs.append({
            'address': self.coinjoin_address(),
            'value': self.cjamount
        })
        random.shuffle(self.utxo_tx)
        random.shuffle(self.outputs)
        tx = btc.mktx(self.utxo_tx, self.outputs)
        jlog.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx)))

        self.latest_tx = btc.deserialize(tx)
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            # placeholders required
            ins['script'] = 'deadbeef'
        self.taker_info_callback("INFO",
                                 "Built tx, sending to counterparties.")
        return (True, self.maker_utxo_data.keys(), tx)
 def run(self):
     st = int(time.time())
     unconfirmed_txid = None
     unconfirmed_txhex = None
     while not unconfirmed_txid:
         time.sleep(unconfirm_poll_period)
         if int(time.time()) - st > unconfirm_timeout:
             log.debug('checking for unconfirmed tx timed out')
             return
         shared_txid = None
         for a in self.output_addresses:
             unconftx = self.blockchaininterface.get_from_electrum('blockchain.address.get_mempool', a).get('result')
             unconftxs = set([str(t['tx_hash']) for t in unconftx])
             if not shared_txid:
                 shared_txid = unconftxs
             else:
                 shared_txid = shared_txid.intersection(unconftxs)
         log.debug('sharedtxid = ' + str(shared_txid))
         if len(shared_txid) == 0:
             continue
         data = []
         for txid in shared_txid:
             txdata = str(self.blockchaininterface.get_from_electrum('blockchain.transaction.get', txid).get('result'))
             data.append({'hex':txdata,'id':txid})
         for txdata in data:
             txhex = txdata['hex']
             outs = set([(sv['script'], sv['value']) for sv in btc.deserialize(txhex)['outs']])
             log.debug('unconfirm query outs = ' + str(outs))
             if outs == self.tx_output_set:
                 unconfirmed_txid = txdata['id']
                 unconfirmed_txhex = txhex
                 break
     self.unconfirmfun(btc.deserialize(unconfirmed_txhex), unconfirmed_txid)
     st = int(time.time())
     confirmed_txid = None
     confirmed_txhex = None
     while not confirmed_txid:
         time.sleep(confirm_poll_period)
         if int(time.time()) - st > confirm_timeout:
             log.debug('checking for confirmed tx timed out')
             return
         shared_txid = None
         for a in self.output_addresses:
             conftx = self.blockchaininterface.get_from_electrum('blockchain.address.listunspent', a).get('result')
             conftxs = set([str(t['tx_hash']) for t in conftx])
             if not shared_txid:
                 shared_txid = conftxs
             else:
                 shared_txid = shared_txid.intersection(conftxs)
         log.debug('sharedtxid = ' + str(shared_txid))
         if len(shared_txid) == 0:
             continue
         data = []
         for txid in shared_txid:
             txdata = str(self.blockchaininterface.get_from_electrum('blockchain.transaction.get', txid).get('result'))
             data.append({'hex':txdata,'id':txid})
         for txdata in data:
             txhex = txdata['hex']
             outs = set([(sv['script'], sv['value']) for sv in btc.deserialize(txhex)['outs']])
             log.debug('confirm query outs = ' + str(outs))
             if outs == self.tx_output_set:
                 confirmed_txid = txdata['id']
                 confirmed_txhex = txhex
                 break
     self.confirmfun(btc.deserialize(confirmed_txhex), confirmed_txid, 1)
Exemplo n.º 17
0
    def recv_txio(self, nick, utxo_list, cj_pub, change_addr):
        if nick not in self.nonrespondants:
            log.debug(('recv_txio => nick={} not in '
                       'nonrespondants {}').format(nick, self.nonrespondants))
            return
        self.utxos[nick] = utxo_list
        log.debug("Trying to retrieve utxos for: " + nick)
        utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick])
        log.debug("Got this utxo data: " + pprint.pformat(utxo_data))
        if None in utxo_data:
            log.debug(('ERROR outputs unconfirmed or already spent. '
                       'utxo_data={}').format(pprint.pformat(utxo_data)))
            # when internal reviewing of makers is created, add it here to
            # immediately quit; currently, the timeout thread suffices.
            return

        total_input = sum([d['value'] for d in utxo_data])
        real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'],
                                 self.active_orders[nick]['cjfee'],
                                 self.cj_amount)
        change_amount = (total_input - self.cj_amount -
                         self.active_orders[nick]['txfee'] + real_cjfee)

        # certain malicious and/or incompetent liquidity providers send
        # inputs totalling less than the coinjoin amount! this leads to
        # a change output of zero satoshis, so the invalid transaction
        # fails harmlessly; let's fail earlier, with a clear message.
        if change_amount < jm_single().DUST_THRESHOLD:
            fmt = ('ERROR counterparty requires sub-dust change. nick={}'
                   'totalin={:d} cjamount={:d} change={:d}').format
            log.debug(fmt(nick, total_input, self.cj_amount, change_amount))
            return  # timeout marks this maker as nonresponsive

        self.outputs.append({'address': change_addr, 'value': change_amount})
        fmt = ('fee breakdown for {} totalin={:d} '
               'cjamount={:d} txfee={:d} realcjfee={:d}').format
        log.debug(
            fmt(nick, total_input, self.cj_amount,
                self.active_orders[nick]['txfee'], real_cjfee))
        cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte())
        self.outputs.append({'address': cj_addr, 'value': self.cj_amount})
        self.cjfee_total += real_cjfee
        self.maker_txfee_contributions += self.active_orders[nick]['txfee']
        self.nonrespondants.remove(nick)
        if len(self.nonrespondants) > 0:
            log.debug('nonrespondants = ' + str(self.nonrespondants))
            return
        log.debug('got all parts, enough to build a tx')
        self.nonrespondants = list(self.active_orders.keys())

        my_total_in = sum(
            [va['value'] for u, va in self.input_utxos.iteritems()])
        if self.my_change_addr:
            #Estimate fee per choice of next/3/6 blocks targetting.
            estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])),
                                            len(self.outputs) + 2)
            log.debug("Based on initial guess: " + str(self.total_txfee) +
                      ", we estimated a fee of: " + str(estimated_fee))
            #reset total
            self.total_txfee = estimated_fee
        my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0)
        my_change_value = (my_total_in - self.cj_amount - self.cjfee_total -
                           my_txfee)
        #Since we could not predict the maker's inputs, we may end up needing
        #too much such that the change value is negative or small. Note that
        #we have tried to avoid this based on over-estimating the needed amount
        #in SendPayment.create_tx(), but it is still a possibility if one maker
        #uses a *lot* of inputs.
        if self.my_change_addr and my_change_value <= 0:
            raise ValueError("Calculated transaction fee of: " +
                             str(self.total_txfee) +
                             " is too large for our inputs;Please try again.")
        elif self.my_change_addr and my_change_value <= jm_single(
        ).DUST_THRESHOLD:
            log.debug("Dynamically calculated change lower than dust: " +
                      str(my_change_value) + "; dropping.")
            self.my_change_addr = None
            my_change_value = 0
        log.debug(
            'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d'
            % (my_total_in, my_txfee, self.maker_txfee_contributions,
               self.cjfee_total, my_change_value))
        if self.my_change_addr is None:
            if my_change_value != 0 and abs(my_change_value) != 1:
                # seems you wont always get exactly zero because of integer
                # rounding so 1 satoshi extra or fewer being spent as miner
                # fees is acceptable
                log.debug(('WARNING CHANGE NOT BEING '
                           'USED\nCHANGEVALUE = {}').format(my_change_value))
        else:
            self.outputs.append({
                'address': self.my_change_addr,
                'value': my_change_value
            })
        self.utxo_tx = [
            dict([('output', u)]) for u in sum(self.utxos.values(), [])
        ]
        self.outputs.append({
            'address': self.coinjoin_address(),
            'value': self.cj_amount
        })
        random.shuffle(self.utxo_tx)
        random.shuffle(self.outputs)
        tx = btc.mktx(self.utxo_tx, self.outputs)
        log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx)))
        #Re-calculate a sensible timeout wait based on the throttling
        #settings and the tx size.
        #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S
        #So we're sending N*1.8*S over the wire, and the
        #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds,
        #and need to add some leeway for network delays, we just add the
        #contents of jm_single().maker_timeout_sec (the user configured value)
        self.maker_timeout_sec = (len(tx) * 1.8 * len(self.active_orders.keys(
        ))) / (B_PER_SEC) + jm_single().maker_timeout_sec
        log.debug("Based on transaction size: " + str(len(tx)) +
                  ", calculated time to wait for replies: " +
                  str(self.maker_timeout_sec))
        self.all_responded = True
        with self.timeout_lock:
            self.timeout_lock.notify()
        self.msgchan.send_tx(self.active_orders.keys(), tx)

        self.latest_tx = btc.deserialize(tx)
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            # placeholders required
            ins['script'] = 'deadbeef'
Exemplo n.º 18
0
            def run(self):
                st = int(time.time())
                unconfirmed_txid = None
                unconfirmed_txhex = None
                while not unconfirmed_txid:
                    time.sleep(unconfirm_poll_period)
                    if int(time.time()) - st > unconfirm_timeout:
                        log.debug('checking for unconfirmed tx timed out')
                        if self.timeoutfun:
                            self.timeoutfun(False)
                        return
                    blockr_url = 'https://' + self.blockr_domain
                    blockr_url += '.blockr.io/api/v1/address/unspent/'
                    random.shuffle(self.output_addresses
                                   )  # seriously weird bug with blockr.io
                    data = json.loads(
                            btc.make_request(blockr_url + ','.join(
                                    self.output_addresses
                            ) + '?unconfirmed=1'))['data']

                    shared_txid = None
                    for unspent_list in data:
                        txs = set([str(txdata['tx'])
                                   for txdata in unspent_list['unspent']])
                        if not shared_txid:
                            shared_txid = txs
                        else:
                            shared_txid = shared_txid.intersection(txs)
                    log.debug('sharedtxid = ' + str(shared_txid))
                    if len(shared_txid) == 0:
                        continue
                    time.sleep(
                            2
                    )  # here for some race condition bullshit with blockr.io
                    blockr_url = 'https://' + self.blockr_domain
                    blockr_url += '.blockr.io/api/v1/tx/raw/'
                    data = json.loads(btc.make_request(blockr_url + ','.join(
                            shared_txid)))['data']
                    if not isinstance(data, list):
                        data = [data]
                    for txinfo in data:
                        txhex = str(txinfo['tx']['hex'])
                        outs = set([(sv['script'], sv['value'])
                                    for sv in btc.deserialize(txhex)['outs']])
                        log.debug('unconfirm query outs = ' + str(outs))
                        if outs == self.tx_output_set:
                            unconfirmed_txid = txinfo['tx']['txid']
                            unconfirmed_txhex = str(txinfo['tx']['hex'])
                            break

                self.unconfirmfun(
                        btc.deserialize(unconfirmed_txhex), unconfirmed_txid)

                st = int(time.time())
                confirmed_txid = None
                confirmed_txhex = None
                while not confirmed_txid:
                    time.sleep(confirm_poll_period)
                    if int(time.time()) - st > confirm_timeout:
                        log.debug('checking for confirmed tx timed out')
                        if self.timeoutfun:
                            self.timeoutfun(True)
                        return
                    blockr_url = 'https://' + self.blockr_domain
                    blockr_url += '.blockr.io/api/v1/address/txs/'
                    data = json.loads(btc.make_request(blockr_url + ','.join(
                            self.output_addresses)))['data']
                    shared_txid = None
                    for addrtxs in data:
                        txs = set(str(txdata['tx'])
                                  for txdata in addrtxs['txs'])
                        if not shared_txid:
                            shared_txid = txs
                        else:
                            shared_txid = shared_txid.intersection(txs)
                    log.debug('sharedtxid = ' + str(shared_txid))
                    if len(shared_txid) == 0:
                        continue
                    blockr_url = 'https://' + self.blockr_domain
                    blockr_url += '.blockr.io/api/v1/tx/raw/'
                    data = json.loads(
                            btc.make_request(
                                    blockr_url + ','.join(shared_txid)))['data']
                    if not isinstance(data, list):
                        data = [data]
                    for txinfo in data:
                        txhex = str(txinfo['tx']['hex'])
                        outs = set([(sv['script'], sv['value'])
                                    for sv in btc.deserialize(txhex)['outs']])
                        log.debug('confirm query outs = ' + str(outs))
                        if outs == self.tx_output_set:
                            confirmed_txid = txinfo['tx']['txid']
                            confirmed_txhex = str(txinfo['tx']['hex'])
                            break
                self.confirmfun(
                        btc.deserialize(confirmed_txhex), confirmed_txid, 1)
Exemplo n.º 19
0
    def recv_txio(self, nick, utxo_list, cj_pub, change_addr):
        if nick not in self.nonrespondants:
            log.debug(('recv_txio => nick={} not in '
                       'nonrespondants {}').format(nick, self.nonrespondants))
            return
        self.utxos[nick] = utxo_list
        log.debug("Trying to retrieve utxos for: " + nick)
        utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick])
        log.debug("Got this utxo data: " + pprint.pformat(utxo_data))
        if None in utxo_data:
            log.debug(('ERROR outputs unconfirmed or already spent. '
                       'utxo_data={}').format(pprint.pformat(utxo_data)))
            # when internal reviewing of makers is created, add it here to
            # immediately quit; currently, the timeout thread suffices.
            return

        total_input = sum([d['value'] for d in utxo_data])
        real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'],
                       self.active_orders[nick]['cjfee'], self.cj_amount)
        change_amount = (total_input - self.cj_amount -
            self.active_orders[nick]['txfee'] + real_cjfee)

        # certain malicious and/or incompetent liquidity providers send
        # inputs totalling less than the coinjoin amount! this leads to
        # a change output of zero satoshis, so the invalid transaction
        # fails harmlessly; let's fail earlier, with a clear message.
        if change_amount < jm_single().DUST_THRESHOLD:
            fmt = ('ERROR counterparty requires sub-dust change. nick={}'
                   'totalin={:d} cjamount={:d} change={:d}').format
            log.debug(fmt(nick, total_input, self.cj_amount, change_amount))
            return              # timeout marks this maker as nonresponsive

        self.outputs.append({'address': change_addr, 'value': change_amount})
        fmt = ('fee breakdown for {} totalin={:d} '
               'cjamount={:d} txfee={:d} realcjfee={:d}').format
        log.debug(fmt(nick, total_input, self.cj_amount,
            self.active_orders[nick]['txfee'], real_cjfee))
        cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte())
        self.outputs.append({'address': cj_addr, 'value': self.cj_amount})
        self.cjfee_total += real_cjfee
        self.maker_txfee_contributions += self.active_orders[nick]['txfee']
        self.nonrespondants.remove(nick)
        if len(self.nonrespondants) > 0:
            log.debug('nonrespondants = ' + str(self.nonrespondants))
            return
        log.debug('got all parts, enough to build a tx')
        self.nonrespondants = list(self.active_orders.keys())

        my_total_in = sum([va['value'] for u, va in
                           self.input_utxos.iteritems()])
        if self.my_change_addr:
            #Estimate fee per choice of next/3/6 blocks targetting.
            estimated_fee = estimate_tx_fee(len(sum(
                self.utxos.values(),[])), len(self.outputs)+2)
            log.debug("Based on initial guess: "+str(
                self.total_txfee)+", we estimated a fee of: "+str(estimated_fee))
            #reset total
            self.total_txfee = estimated_fee
        my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0)
        my_change_value = (
            my_total_in - self.cj_amount - self.cjfee_total - my_txfee)
        #Since we could not predict the maker's inputs, we may end up needing
        #too much such that the change value is negative or small. Note that 
        #we have tried to avoid this based on over-estimating the needed amount
        #in SendPayment.create_tx(), but it is still a possibility if one maker
        #uses a *lot* of inputs.
        if self.my_change_addr and my_change_value <= 0:
            raise ValueError("Calculated transaction fee of: "+str(
                self.total_txfee)+" is too large for our inputs;Please try again.")
        elif self.my_change_addr and my_change_value <= jm_single().DUST_THRESHOLD:
            log.debug("Dynamically calculated change lower than dust: "+str(
                my_change_value)+"; dropping.")
            self.my_change_addr = None
            my_change_value = 0
        log.debug('fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d'
                  % (my_total_in, my_txfee, self.maker_txfee_contributions,            
                  self.cjfee_total, my_change_value))
        if self.my_change_addr is None:
            if my_change_value != 0 and abs(my_change_value) != 1:
                # seems you wont always get exactly zero because of integer
                # rounding so 1 satoshi extra or fewer being spent as miner
                # fees is acceptable
                log.debug(('WARNING CHANGE NOT BEING '
                           'USED\nCHANGEVALUE = {}').format(my_change_value))
        else:
            self.outputs.append({'address': self.my_change_addr,
                                 'value': my_change_value})
        self.utxo_tx = [dict([('output', u)])
                        for u in sum(self.utxos.values(), [])]
        self.outputs.append({'address': self.coinjoin_address(),
                             'value': self.cj_amount})
        random.shuffle(self.utxo_tx)
        random.shuffle(self.outputs)
        tx = btc.mktx(self.utxo_tx, self.outputs)
        log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx)))
        #Re-calculate a sensible timeout wait based on the throttling
        #settings and the tx size.
        #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S
        #So we're sending N*1.8*S over the wire, and the
        #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds,
        #and need to add some leeway for network delays, we just add the
        #contents of jm_single().maker_timeout_sec (the user configured value)
        self.maker_timeout_sec = (len(tx) * 1.8 * len(
            self.active_orders.keys()))/(B_PER_SEC) + jm_single().maker_timeout_sec
        log.debug("Based on transaction size: " + str(
            len(tx)) + ", calculated time to wait for replies: " + str(
            self.maker_timeout_sec))
        self.all_responded = True
        with self.timeout_lock:
            self.timeout_lock.notify()
        self.msgchan.send_tx(self.active_orders.keys(), tx)

        self.latest_tx = btc.deserialize(tx)
        for index, ins in enumerate(self.latest_tx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                    ins['outpoint']['index'])
            if utxo not in self.input_utxos.keys():
                continue
            # placeholders required
            ins['script'] = 'deadbeef'