# Buggy code
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: " + nick)
            # 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.warn(
                ("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 = self.wallet.pubkey_to_address(auth_pub)
        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.warn(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.info(
            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,
            txtype=self.wallet.get_txtype(),
        )
        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.info(
                ("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.info("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)


# Traceback (exceptions.AttributeError)
2018-07-21 00:03:59,900 [MainThread  ] [INFO ]  Makers didnt respond: [u'J57XBPc4Ub5b3xxO', u'J54nByhefebjZFkB']
2018-07-21 00:21:32,779 [MainThread  ] [INFO ]  STALL MONITOR:
2018-07-21 00:21:32,779 [MainThread  ] [INFO ]  Stall detected. Regenerating transactions and retrying.
Unhandled Error
Traceback (most recent call last):
File "sendpayment.py", line 233, in main
clientfactory, daemon=daemon)
File "/home/alex/Documents/joinmarket-dev/test_ranr-under-max/joinmarket-clientserver/jmvenv/local/lib/python2.7/site-packages/jmclient/client_protocol.py", line 495, in start_reactor
reactor.run(installSignalHandlers=ish)
File "/home/alex/Documents/joinmarket-dev/test_ranr-under-max/joinmarket-clientserver/jmvenv/local/lib/python2.7/site-packages/twisted/internet/base.py", line 1199, in run
self.mainLoop()
File "/home/alex/Documents/joinmarket-dev/test_ranr-under-max/joinmarket-clientserver/jmvenv/local/lib/python2.7/site-packages/twisted/internet/base.py", line 1208, in mainLoop
self.runUntilCurrent()
--- <exception caught here> ---
File "/home/alex/Documents/joinmarket-dev/test_ranr-under-max/joinmarket-clientserver/jmvenv/local/lib/python2.7/site-packages/twisted/internet/base.py", line 828, in runUntilCurrent
call.func(*call.args, **call.kw)
File "/home/alex/Documents/joinmarket-dev/test_ranr-under-max/joinmarket-clientserver/jmvenv/local/lib/python2.7/site-packages/jmclient/client_protocol.py", line 346, in stallMonitor
self.client.on_finished_callback(False, True, 0.0)
File "sendpayment.py", line 189, in taker_finished
"makers didn't respond: ", taker.nonrespondants,
exceptions.AttributeError: 'Taker' object has no attribute 'nonrespondants'

# Fixed code
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")

    # Temporary list used to aggregate all ioauth data that must be removed
    rejected_counterparties = []
    # 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: " + nick)
            # 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.warn(
                ("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 = self.wallet.pubkey_to_address(auth_pub)
        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.warn(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.info(
            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
        # We have succesfully processed the data from this nick:
        try:
            self.nonrespondants.remove(nick)
        except Exception as e:
            jlog.warn(
                "Failure to remove counterparty from nonrespondants list: "
                + str(nick)
                + ", error message: "
                + repr(e)
            )

    # Apply business logic of how many counterparties are enough; note that
    # this must occur after the above ioauth data processing, since we only now
    # know for sure that the data meets all business-logic requirements.
    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")

    # The list self.nonrespondants is now reset and
    # used to track return of signatures for phase 2
    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,
            txtype=self.wallet.get_txtype(),
        )
        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.info(
                ("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.info("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)