def calculate_fidelity_bond_values(fidelity_bonds_info):
    if len(fidelity_bonds_info) == 0:
        return {}
    interest_rate = get_interest_rate()
    blocks = jm_single().bc_interface.get_current_block_height()
    mediantime = jm_single().bc_interface.get_best_block_median_time()

    validated_bonds = {}
    for bond_data in fidelity_bonds_info:
        try:
            fb_proof = FidelityBondProof.parse_and_verify_proof_msg(
                bond_data["counterparty"], bond_data["takernick"],
                bond_data["proof"])
        except ValueError:
            continue
        if fb_proof.utxo in validated_bonds:
            continue
        utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
            fb_proof.utxo, fb_proof.utxo_pub, fb_proof.locktime,
            fb_proof.cert_expiry, blocks)
        if utxo_data is not None:
            validated_bonds[fb_proof.utxo] = (fb_proof, utxo_data)

    fidelity_bond_values = {
        bond_data.maker_nick:
        FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
            utxo_data["value"],
            jm_single().bc_interface.get_block_time(
                jm_single().bc_interface.get_block_hash(blocks -
                                                        utxo_data["confirms"] +
                                                        1)),
            bond_data.locktime, mediantime, interest_rate)
        for bond_data, utxo_data in validated_bonds.values()
    }
    return fidelity_bond_values
def test_fidelity_bond_seen(valid, fidelity_bond_proof, maker_nick,
                            taker_nick):
    proof = FidelityBondProof(
        maker_nick, taker_nick,
        hextobin(fidelity_bond_proof['certificate-pubkey']),
        fidelity_bond_proof['certificate-expiry'],
        hextobin(fidelity_bond_proof['certificate-signature']),
        (hextobin(fidelity_bond_proof['txid']), fidelity_bond_proof['vout']),
        hextobin(fidelity_bond_proof['utxo-pubkey']),
        fidelity_bond_proof['locktime'])
    serialized = proof._serialize_proof_msg(
        fidelity_bond_proof['nick-signature'])

    ob = get_ob()
    ob.msgchan.nick = taker_nick
    ob.on_fidelity_bond_seen(maker_nick, fidelity_bond_cmd_list[0], serialized)
    rows = ob.db.execute("SELECT * FROM fidelitybonds;").fetchall()
    assert len(rows) == 1
    assert rows[0]["counterparty"] == maker_nick
    assert rows[0]["takernick"] == taker_nick
    try:
        parsed_proof = FidelityBondProof.parse_and_verify_proof_msg(
            rows[0]["counterparty"], rows[0]["takernick"], rows[0]["proof"])
    except ValueError:
        parsed_proof = None
    if valid:
        assert parsed_proof is not None
        assert parsed_proof.utxo[0] == hextobin(fidelity_bond_proof["txid"])
        assert parsed_proof.utxo[1] == fidelity_bond_proof["vout"]
        assert parsed_proof.locktime == fidelity_bond_proof["locktime"]
        assert parsed_proof.cert_expiry == fidelity_bond_proof[
            "certificate-expiry"]
        assert parsed_proof.utxo_pub == hextobin(
            fidelity_bond_proof["utxo-pubkey"])
    else:
        assert parsed_proof is None
def get_fidelity_bond_data(taker):
    with taker.dblock:
        fbonds = taker.db.execute("SELECT * FROM fidelitybonds;").fetchall()

    blocks = jm_single().bc_interface.get_current_block_height()
    mediantime = jm_single().bc_interface.get_best_block_median_time()
    interest_rate = get_interest_rate()

    bond_utxo_set = set()
    fidelity_bond_data = []
    bond_outpoint_conf_times = []
    fidelity_bond_values = []
    for fb in fbonds:
        try:
            parsed_bond = FidelityBondProof.parse_and_verify_proof_msg(
                fb["counterparty"], fb["takernick"], fb["proof"])
        except ValueError:
            continue
        bond_utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
            parsed_bond.utxo, parsed_bond.utxo_pub, parsed_bond.locktime,
            parsed_bond.cert_expiry, blocks)
        if bond_utxo_data == None:
            continue
        #check for duplicated utxos i.e. two or more makers using the same UTXO
        # which is obviously not allowed, a fidelity bond must only be usable by one maker nick
        utxo_str = parsed_bond.utxo[0] + b":" + str(
            parsed_bond.utxo[1]).encode("ascii")
        if utxo_str in bond_utxo_set:
            continue
        bond_utxo_set.add(utxo_str)

        fidelity_bond_data.append((parsed_bond, bond_utxo_data))
        conf_time = jm_single().bc_interface.get_block_time(
            jm_single().bc_interface.get_block_hash(
                blocks - bond_utxo_data["confirms"] + 1))
        bond_outpoint_conf_times.append(conf_time)

        bond_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
            bond_utxo_data["value"], conf_time, parsed_bond.locktime,
            mediantime, interest_rate)
        fidelity_bond_values.append(bond_value)
    return (fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times)
def test_duplicate_fidelity_bond_rejected():

    fidelity_bond_info = (({
        "nick-signature":
        (b'0E\x02!\x00\xdbb\x15\x96\xa0\x87\xb8\x1d\xe05\xddV\xa1\x1bn\x8f' +
         b'q\x90&\x8cG@\x89"2\xb2\x81\x9b\xc00\xa5\xb6\x02 \x03\x14l\xd7BR\xba\x8c:\x88('
         +
         b'\x8e3l\xac\xf5`T\x87\xfa\xf5\xa9\x1f\x19\xc0\xb6\xe9\xbb\xdc\xc7y\x99'
         ),
        "certificate-signature":
        ("3045022100eb512af938113badb4d7b29e0c22061c51dadb113a9395e" +
         "9ed81a46103391213022029170de414964f07228c4f0d404b1386272bae337f0133f1329d948a"
         + "252fa2a0"),
        "certificate-pubkey":
        "0258efb077960d6848f001904857f062fa453de26c1ad8736f55497254f56e8a74",
        "certificate-expiry":
        1,
        "utxo-pubkey":
        "02f54f027377e84171296453828aa863c23fc4489453025f49bd3addfb3a359b3d",
        "txid":
        "84c88fafe0bb75f507fe3bfb29a93d10b2e80c15a63b2943c1a5fecb5a55cba2",
        "vout":
        0,
        "locktime":
        1640995200
    }, "J5A4k9ecQzRRDfBx", "J55VZ6U6ZyFDNeuv"), ({
        "nick-signature":
        (b'0E\x02!\x00\x80\xc6$\x0c\xa1\x15YS\xacHB\xb33\xfa~\x9f\xb9`\xb3' +
         b'\xfe\xed0\xadHq\xc1~\x03.B\xbb#\x02 #y~]\xd9\xbbX2\xc0\x1b\xe57\xf4\x0f\x1f'
         + b'\xd6$\x01\xf9\x15Z\xc9X\xa5\x18\xbe\x83\x1a&4Y\xd4'),
        "certificate-signature":
        ("304402205669ea394f7381e9abf0b3c013fac2b79d24c02feb86ff153" +
         "cff83c658d7cf7402200b295ace655687f80738f3733c1dc5f1e2b8f351c017a05b8bd31983dd"
         + "4d723f"),
        "certificate-pubkey":
        "031d1c006a6310dbdf57341efc19c3a43c402379d7ccd2480416cadc7579f973f7",
        "certificate-expiry":
        1,
        "utxo-pubkey":
        "02616c56412eb738a9eacfb0550b43a5a2e77e5d5205ea9e2ca8dfac34e50c9754",
        "txid":
        "84c88fafe0bb75f507fe3bfb29a93d10b2e80c15a63b2943c1a5fecb5a55cba2",
        "vout":
        1,
        "locktime":
        1893456000
    }, "J54LS6YyJPoseqFS", "J55VZ6U6ZyFDNeuv"))

    ob = get_ob()

    fidelity_bond_proof1, maker_nick1, taker_nick1 = fidelity_bond_info[0]
    proof = FidelityBondProof(
        maker_nick1, taker_nick1,
        hextobin(fidelity_bond_proof1['certificate-pubkey']),
        fidelity_bond_proof1['certificate-expiry'],
        hextobin(fidelity_bond_proof1['certificate-signature']),
        (hextobin(fidelity_bond_proof1['txid']), fidelity_bond_proof1['vout']),
        hextobin(fidelity_bond_proof1['utxo-pubkey']),
        fidelity_bond_proof1['locktime'])
    serialized1 = proof._serialize_proof_msg(
        fidelity_bond_proof1['nick-signature'])
    ob.msgchan.nick = taker_nick1

    ob.on_fidelity_bond_seen(maker_nick1, fidelity_bond_cmd_list[0],
                             serialized1)
    rows = ob.db.execute("SELECT * FROM fidelitybonds;").fetchall()
    assert len(rows) == 1

    #show the same fidelity bond message again, check it gets rejected as duplicate
    ob.on_fidelity_bond_seen(maker_nick1, fidelity_bond_cmd_list[0],
                             serialized1)
    rows = ob.db.execute("SELECT * FROM fidelitybonds;").fetchall()
    assert len(rows) == 1

    #show a different fidelity bond and check it does get accepted
    fidelity_bond_proof2, maker_nick2, taker_nick2 = fidelity_bond_info[1]
    proof2 = FidelityBondProof(
        maker_nick1, taker_nick1,
        hextobin(fidelity_bond_proof2['certificate-pubkey']),
        fidelity_bond_proof2['certificate-expiry'],
        hextobin(fidelity_bond_proof2['certificate-signature']),
        (hextobin(fidelity_bond_proof2['txid']), fidelity_bond_proof2['vout']),
        hextobin(fidelity_bond_proof2['utxo-pubkey']),
        fidelity_bond_proof2['locktime'])
    serialized2 = proof2._serialize_proof_msg(
        fidelity_bond_proof2['nick-signature'])
    ob.msgchan.nick = taker_nick2

    ob.on_fidelity_bond_seen(maker_nick2, fidelity_bond_cmd_list[0],
                             serialized2)
    rows = ob.db.execute("SELECT * FROM fidelitybonds;").fetchall()
    assert len(rows) == 2
    def create_orderbook_table(self, btc_unit, rel_unit):
        result = ''
        try:
            self.taker.dblock.acquire(True)
            rows = self.taker.db.execute('SELECT * FROM orderbook;').fetchall()
        finally:
            self.taker.dblock.release()
        if not rows:
            return 0, result
        rows = [o for o in rows if o["ordertype"] in filtered_offername_list]

        if jm_single().bc_interface == None:
            for row in rows:
                row["bondvalue"] = "No data"
        else:
            blocks = jm_single().bc_interface.get_current_block_height()
            mediantime = jm_single().bc_interface.get_best_block_median_time()
            interest_rate = get_interest_rate()
            for row in rows:
                with self.taker.dblock:
                    fbond_data = self.taker.db.execute(
                        "SELECT * FROM fidelitybonds WHERE counterparty=?;",
                        (row["counterparty"], )).fetchall()
                if len(fbond_data) == 0:
                    row["bondvalue"] = "0"
                    continue
                else:
                    try:
                        parsed_bond = FidelityBondProof.parse_and_verify_proof_msg(
                            fbond_data[0]["counterparty"],
                            fbond_data[0]["takernick"], fbond_data[0]["proof"])
                    except ValueError:
                        row["bondvalue"] = "0"
                        continue
                    utxo_data = FidelityBondMixin.get_validated_timelocked_fidelity_bond_utxo(
                        parsed_bond.utxo, parsed_bond.utxo_pub,
                        parsed_bond.locktime, parsed_bond.cert_expiry, blocks)
                    if utxo_data == None:
                        row["bondvalue"] = "0"
                        continue
                    bond_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
                        utxo_data["value"],
                        jm_single().bc_interface.get_block_time(
                            jm_single().bc_interface.get_block_hash(
                                blocks - utxo_data["confirms"] + 1)),
                        parsed_bond.locktime, mediantime, interest_rate)
                    row["bondvalue"] = satoshi_to_unit_power(
                        bond_value, 2 * unit_to_power[btc_unit])

        order_keys_display = (('ordertype', ordertype_display),
                              ('counterparty', do_nothing), ('oid', order_str),
                              ('cjfee', cjfee_display),
                              ('txfee', satoshi_to_unit), ('minsize',
                                                           satoshi_to_unit),
                              ('maxsize', satoshi_to_unit), ('bondvalue',
                                                             do_nothing))

        # somewhat complex sorting to sort by cjfee but with swabsoffers on top

        def orderby_cmp(x, y):
            if x['ordertype'] == y['ordertype']:
                return cmp(Decimal(x['cjfee']), Decimal(y['cjfee']))
            return cmp(offername_list.index(x['ordertype']),
                       offername_list.index(y['ordertype']))

        for o in sorted(rows, key=cmp_to_key(orderby_cmp)):
            result += ' <tr>\n'
            for key, displayer in order_keys_display:
                result += '  <td>' + displayer(o[key], o, btc_unit,
                                               rel_unit) + '</td>\n'
            result += ' </tr>\n'
        return len(rows), result
    def create_fidelity_bond_table(self, btc_unit):
        if jm_single().bc_interface == None:
            with self.taker.dblock:
                fbonds = self.taker.db.execute(
                    "SELECT * FROM fidelitybonds;").fetchall()
            fidelity_bond_data = []
            for fb in fbonds:
                try:
                    proof = FidelityBondProof.parse_and_verify_proof_msg(
                        fb["counterparty"], fb["takernick"], fb["proof"])
                except ValueError:
                    proof = None
                fidelity_bond_data.append((proof, None))
            fidelity_bond_values = [-1] * len(
                fidelity_bond_data)  #-1 means no data
            bond_outpoint_conf_times = [-1] * len(fidelity_bond_data)
            total_btc_committed_str = "unknown"
        else:
            (fidelity_bond_data, fidelity_bond_values, bond_outpoint_conf_times) =\
                get_fidelity_bond_data(self.taker)
            total_btc_committed_str = satoshi_to_unit(
                sum([
                    utxo_data["value"] for _, utxo_data in fidelity_bond_data
                ]), None, btc_unit, 0)

        RETARGET_INTERVAL = 2016
        elem = lambda e: "<td>" + e + "</td>"
        bondtable = ""
        for (bond_data, utxo_data), bond_value, conf_time in zip(
                fidelity_bond_data, fidelity_bond_values,
                bond_outpoint_conf_times):

            if bond_value == -1 or conf_time == -1 or utxo_data == None:
                bond_value_str = "No data"
                conf_time_str = "No data"
                utxo_value_str = "No data"
            else:
                bond_value_str = satoshi_to_unit_power(
                    bond_value, 2 * unit_to_power[btc_unit])
                conf_time_str = str(
                    datetime.utcfromtimestamp(0) +
                    timedelta(seconds=conf_time))
                utxo_value_str = satoshi_to_unit(utxo_data["value"], None,
                                                 btc_unit, 0)
            bondtable += (
                "<tr>" + elem(bond_data.maker_nick) + elem(
                    bintohex(bond_data.utxo[0]) + ":" + str(bond_data.utxo[1]))
                + elem(bond_value_str) + elem(
                    (datetime.utcfromtimestamp(0) + timedelta(
                        seconds=bond_data.locktime)).strftime("%Y-%m-%d")) +
                elem(utxo_value_str) + elem(conf_time_str) +
                elem(str(bond_data.cert_expiry * RETARGET_INTERVAL)) + elem(
                    bintohex(
                        btc.mk_freeze_script(bond_data.utxo_pub,
                                             bond_data.locktime))) + "</tr>")

        heading2 = (str(len(fidelity_bond_data)) +
                    " fidelity bonds found with " + total_btc_committed_str +
                    " " + btc_unit + " total locked up")
        choose_units_form = (
            '<form method="get" action="">' +
            '<select name="btcunit" onchange="this.form.submit();">' + ''.join(
                ('<option>' + u + ' </option>'
                 for u in sorted_units)) + '</select></form>')
        choose_units_form = choose_units_form.replace(
            '<option>' + btc_unit, '<option selected="selected">' + btc_unit)

        decodescript_tip = (
            "<br/>Tip: try running the RPC <code>decodescript " +
            "&lt;redeemscript&gt;</code> as proof that the fidelity bond address matches the "
            +
            "locktime.<br/>Also run <code>gettxout &lt;utxo_txid&gt; &lt;utxo_vout&gt;</code> "
            + "as proof that the fidelity bond UTXO is real.")

        return (heading2,
                choose_units_form + create_bonds_table_heading(btc_unit) +
                bondtable + "</table>" + decodescript_tip)