Esempio n. 1
0
def publish_commits():
    with etc.database_lock:
        commit_rawtxs = []
        cursor = sql.get_cursor()
        for hub_connection in db.hub_connections_complete(cursor=cursor):
            asset = hub_connection["asset"]
            c2h_mpc_id = hub_connection["c2h_channel_id"]
            c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
            h2c_mpc_id = hub_connection["h2c_channel_id"]
            h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
            h2c_spend_secret_hash = get_deposit_spend_secret_hash(
                h2c_state["deposit_script"]
            )
            h2c_spend_secret = lib.get_secret(h2c_spend_secret_hash)
            c2h_expired = lib.is_expired(c2h_state, etc.expire_clearance)
            h2c_expired = lib.is_expired(h2c_state, etc.expire_clearance)
            expired = c2h_expired or h2c_expired
            h2c_commits_published = api.mpc_published_commits(state=h2c_state)
            closed = hub_connection["closed"] != 0

            # connection expired or commit published or spend secret known
            if expired or closed or h2c_commits_published or h2c_spend_secret:
                if not closed:
                    db.set_connection_closed(handle=hub_connection["handle"])
                rawtx = Mpc(api).finalize_commit(lib.get_wif, c2h_state)
                if rawtx:
                    commit_rawtxs.append(rawtx)

        return commit_rawtxs
Esempio n. 2
0
def test_saves_state_revoked(connected_clients):
    alice, bob, charlie, david, eric, fred = connected_clients
    alice.micro_send(bob.handle, 5)
    alice.sync()

    # load alice c2h_state
    connection = db.hub_connection(handle=alice.handle)
    c2h_state = db.load_channel_state(
        connection["c2h_channel_id"], connection["asset"]
    )

    # revoke c2h commit
    revoke_hashes = api.mpc_revoke_hashes_until(state=c2h_state, quantity=0)
    secrets = [lib.get_secret(h) for h in revoke_hashes]
    updated_c2h_state = api.mpc_revoke_all(state=c2h_state, secrets=secrets)
    assert len(updated_c2h_state["commits_active"]) == 0
    assert len(updated_c2h_state["commits_revoked"]) == 1

    # save updated c2h state
    db.save_channel_state(
        connection["c2h_channel_id"], updated_c2h_state,
        unnotified_revoke_secrets=secrets
    )

    # load updated c2h state
    connection = db.hub_connection(handle=alice.handle)
    saved_c2h_state = db.load_channel_state(
        connection["c2h_channel_id"], connection["asset"]
    )

    assert len(saved_c2h_state["commits_active"]) == 0
    assert len(saved_c2h_state["commits_revoked"]) == 1
    assert updated_c2h_state == saved_c2h_state
Esempio n. 3
0
def publish_commits():
    with etc.database_lock:
        txids = []
        cursor = sql.get_cursor()
        for hub_connection in db.hub_connections_complete(cursor=cursor):
            asset = hub_connection["asset"]
            c2h_mpc_id = hub_connection["c2h_channel_id"]
            c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
            h2c_mpc_id = hub_connection["h2c_channel_id"]
            h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
            h2c_spend_secret_hash = get_deposit_spend_secret_hash(
                h2c_state["deposit_script"])
            h2c_spend_secret = lib.get_secret(h2c_spend_secret_hash)
            c2h_expired = lib.is_expired(c2h_state, etc.expire_clearance)
            h2c_expired = lib.is_expired(h2c_state, etc.expire_clearance)
            expired = c2h_expired or h2c_expired
            h2c_commits_published = api.mpc_published_commits(state=h2c_state)
            closed = hub_connection["closed"] != 0

            # connection expired or commit published or spend secret known
            if expired or closed or h2c_commits_published or h2c_spend_secret:
                if not closed:
                    db.set_connection_closed(handle=hub_connection["handle"])
                c2h_commits_published = api.mpc_published_commits(
                    state=c2h_state)
                if len(c2h_commits_published) == 0:
                    txid = Mpc(api).finalize_commit(lib.get_wif, c2h_state)
                    if txid:
                        txids.append(txid)

        return txids
Esempio n. 4
0
def fund_deposits():
    """Fund or top off open channels."""
    with etc.database_lock:
        deposits = []
        cursor = sql.get_cursor()
        for hub_connection in db.hub_connections_open(cursor=cursor):

            asset = hub_connection["asset"]
            terms = db.terms(id=hub_connection["terms_id"], cursor=cursor)

            # load client to hub data
            c2h_mpc_id = hub_connection["c2h_channel_id"]
            c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
            c2h_deposit_address = lib.deposit_address(c2h_state)
            c2h_deposit_balance = lib.get_balances(c2h_deposit_address,
                                                   assets=[asset])[asset]

            if c2h_deposit_balance < terms["deposit_min"]:
                continue  # ignore if client deposit insufficient
            if lib.is_expired(c2h_state, etc.expire_clearance):
                continue  # ignore if expires soon
            if lib.has_unconfirmed_transactions(c2h_deposit_address):
                continue  # ignore if unconfirmed transaction inputs/outputs
            if api.mpc_published_commits(state=c2h_state):
                continue  # ignore if c2h commit published

            # load hub to client data
            h2c_mpc_id = hub_connection["h2c_channel_id"]
            h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
            h2c_deposit_address = lib.deposit_address(h2c_state)
            h2c_deposit_balance = lib.get_balances(h2c_deposit_address,
                                                   assets=[asset])[asset]

            if lib.is_expired(h2c_state, etc.expire_clearance):
                continue  # ignore if expires soon
            if lib.has_unconfirmed_transactions(h2c_deposit_address):
                continue  # ignore if unconfirmed transaction inputs/outputs
            if api.mpc_published_commits(state=h2c_state):
                continue  # ignore if h2c commit published

            # fund hub to client if needed
            deposit_max = terms["deposit_max"]
            deposit_ratio = terms["deposit_ratio"]
            if deposit_max:
                target = min(deposit_max, c2h_deposit_balance) * deposit_ratio
            else:
                target = int(c2h_deposit_balance * deposit_ratio)
            quantity = target - h2c_deposit_balance
            if quantity > 0:
                txid = lib.send_funds(h2c_deposit_address, asset, quantity)
                deposits.append({
                    "txid": txid,
                    "asset": asset,
                    "address": h2c_deposit_address,
                    "quantity": quantity,
                    "handle": hub_connection["handle"]
                })

        return deposits
Esempio n. 5
0
def fund_deposits():
    """Fund or top off open channels."""
    with etc.database_lock:
        deposits = []
        cursor = sql.get_cursor()
        for hub_connection in db.hub_connections_open(cursor=cursor):

            asset = hub_connection["asset"]
            terms = db.terms(id=hub_connection["terms_id"], cursor=cursor)

            # load client to hub data
            c2h_mpc_id = hub_connection["c2h_channel_id"]
            c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
            c2h_deposit_address = lib.deposit_address(c2h_state)
            c2h_deposit_balance = lib.get_balances(c2h_deposit_address,
                                                   assets=[asset])[asset]

            if c2h_deposit_balance < terms["deposit_min"]:
                continue  # ignore if client deposit insufficient
            if lib.is_expired(c2h_state, etc.expire_clearance):
                continue  # ignore if expires soon
            if lib.has_unconfirmed_transactions(c2h_deposit_address):
                continue  # ignore if unconfirmed transaction inputs/outputs
            if api.mpc_published_commits(state=c2h_state):
                continue  # ignore if c2h commit published

            # load hub to client data
            h2c_mpc_id = hub_connection["h2c_channel_id"]
            h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
            h2c_deposit_address = lib.deposit_address(h2c_state)
            h2c_deposit_balance = lib.get_balances(h2c_deposit_address,
                                                   assets=[asset])[asset]

            if lib.is_expired(h2c_state, etc.expire_clearance):
                continue  # ignore if expires soon
            if lib.has_unconfirmed_transactions(h2c_deposit_address):
                continue  # ignore if unconfirmed transaction inputs/outputs
            if api.mpc_published_commits(state=h2c_state):
                continue  # ignore if h2c commit published

            # fund hub to client if needed
            deposit_max = terms["deposit_max"]
            deposit_ratio = terms["deposit_ratio"]
            if deposit_max:
                target = min(deposit_max, c2h_deposit_balance) * deposit_ratio
            else:
                target = int(c2h_deposit_balance * deposit_ratio)
            quantity = target - h2c_deposit_balance
            if quantity > 0:
                txid = lib.send_funds(h2c_deposit_address, asset, quantity)
                deposits.append({
                    "txid": txid,
                    "asset": asset,
                    "address": h2c_deposit_address,
                    "quantity": quantity,
                    "handle": hub_connection["handle"]
                })

        return deposits
Esempio n. 6
0
def recover_funds(hub_connection, cursor=None):
    from picopayments_hub import api
    asset = hub_connection["asset"]
    c2h_mpc_id = hub_connection["c2h_channel_id"]
    h2c_mpc_id = hub_connection["h2c_channel_id"]
    c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
    h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
    return Mpc(api).full_duplex_recover_funds(get_wif, get_secret, c2h_state,
                                              h2c_state)
Esempio n. 7
0
def recover_funds(hub_connection, cursor=None):
    from picopayments_hub import api
    asset = hub_connection["asset"]
    c2h_mpc_id = hub_connection["c2h_channel_id"]
    h2c_mpc_id = hub_connection["h2c_channel_id"]
    c2h_state = db.load_channel_state(c2h_mpc_id, asset, cursor=cursor)
    h2c_state = db.load_channel_state(h2c_mpc_id, asset, cursor=cursor)
    return Mpc(api).full_duplex_recover_funds(
        get_wif, get_secret, c2h_state, h2c_state
    )
Esempio n. 8
0
def close_connection(handle, h2c_spend_secret=None):

    cursor = sql.get_cursor()
    hub_connection = db.hub_connection(handle=handle, cursor=cursor)

    # save secret if given and not already known
    if h2c_spend_secret is not None:
        secret_hash = util.hash160hex(h2c_spend_secret)
        if not get_secret(secret_hash):
            db.add_secret(secret_value=h2c_spend_secret,
                          secret_hash=secret_hash,
                          cursor=cursor)

    # close connection if not already done
    if not hub_connection["closed"]:
        db.set_connection_closed(handle=handle, cursor=cursor)

    # get c2h spend secret if no commits for channel
    c2h_spend_secret = None
    c2h_state = db.load_channel_state(hub_connection["c2h_channel_id"],
                                      hub_connection["asset"],
                                      cursor=cursor)
    if len(c2h_state["commits_active"]) == 0:
        c2h_spend_secret_hash = scripts.get_deposit_spend_secret_hash(
            c2h_state["deposit_script"])
        c2h_spend_secret = get_secret(c2h_spend_secret_hash)

    hub_key = db.channel_payer_key(id=hub_connection["h2c_channel_id"])
    return ({"spend_secret": c2h_spend_secret}, hub_key["wif"])
Esempio n. 9
0
def close_connection(handle, h2c_spend_secret=None):

    cursor = sql.get_cursor()
    hub_connection = db.hub_connection(handle=handle, cursor=cursor)

    # save secret if given and not already known
    if h2c_spend_secret is not None:
        secret_hash = util.hash160hex(h2c_spend_secret)
        if not get_secret(secret_hash):
            db.add_secret(secret_value=h2c_spend_secret,
                          secret_hash=secret_hash, cursor=cursor)

    # close connection if not already done
    if not hub_connection["closed"]:
        db.set_connection_closed(handle=handle, cursor=cursor)

    # get c2h spend secret if no commits for channel
    c2h_spend_secret = None
    c2h_state = db.load_channel_state(hub_connection["c2h_channel_id"],
                                      hub_connection["asset"], cursor=cursor)
    if len(c2h_state["commits_active"]) == 0:
        c2h_spend_secret_hash = scripts.get_deposit_spend_secret_hash(
            c2h_state["deposit_script"]
        )
        c2h_spend_secret = get_secret(c2h_spend_secret_hash)

    hub_wif = load_wif()
    return ({"spend_secret": c2h_spend_secret}, hub_wif)
Esempio n. 10
0
def update_channel_state(channel_id,
                         asset,
                         commit=None,
                         revokes=None,
                         cursor=None):
    from picopayments_hub import api

    state = db.load_channel_state(channel_id, asset, cursor=cursor)
    unnotified_revokes = db.unnotified_revokes(channel_id=channel_id)
    unnotified_commit = db.unnotified_commit(channel_id=channel_id,
                                             cursor=cursor)
    unnotified_revoke_secrets = [
        x["revoke_secret"] for x in unnotified_revokes
    ]
    if commit is not None:
        state = api.mpc_add_commit(state=state,
                                   commit_rawtx=commit["rawtx"],
                                   commit_script=commit["script"])
    if revokes is not None:
        # FIXME will not set revokes as unnotified
        #       currently not a problem as its only used for hub to client
        #       but its begging to be missused!!
        state = api.mpc_revoke_all(state=state, secrets=revokes)
    cursor.execute("BEGIN TRANSACTION;")
    db.save_channel_state(channel_id,
                          state,
                          h2c_unnotified_commit=unnotified_commit,
                          unnotified_revoke_secrets=unnotified_revoke_secrets,
                          cursor=cursor)
    cursor.execute("COMMIT;")
    return state
Esempio n. 11
0
def update_channel_state(channel_id, asset, commit=None,
                         revokes=None, cursor=None):
    from picopayments_hub import api

    state = db.load_channel_state(channel_id, asset, cursor=cursor)
    unnotified_revokes = db.unnotified_revokes(channel_id=channel_id)
    unnotified_commit = db.unnotified_commit(channel_id=channel_id,
                                             cursor=cursor)
    unnotified_revoke_secrets = [x["revoke_secret"]
                                 for x in unnotified_revokes]
    if commit is not None:
        state = api.mpc_add_commit(
            state=state,
            commit_rawtx=commit["rawtx"],
            commit_script=commit["script"]
        )
    if revokes is not None:
        # TODO will not set revokes as unnotified
        #      currently not a problem as its only used for hub to client
        #      but its begging to be missused!!
        state = api.mpc_revoke_all(state=state, secrets=revokes)
    cursor.execute("BEGIN TRANSACTION;")
    db.save_channel_state(
        channel_id, state, h2c_unnotified_commit=unnotified_commit,
        unnotified_revoke_secrets=unnotified_revoke_secrets, cursor=cursor
    )
    cursor.execute("COMMIT;")
    return state
Esempio n. 12
0
def _assert_states_synced(handle, c2h_state, h2c_state):
    connection = db.hub_connection(handle=handle)
    db_h2c_state = db.load_channel_state(connection["h2c_channel_id"],
                                         connection["asset"])
    db_c2h_state = db.load_channel_state(connection["c2h_channel_id"],
                                         connection["asset"])
    assert c2h_state["asset"] == db_c2h_state["asset"]
    assert h2c_state["asset"] == db_h2c_state["asset"]
    assert c2h_state["deposit_script"] == db_c2h_state["deposit_script"]
    assert h2c_state["deposit_script"] == db_h2c_state["deposit_script"]

    c2h_scripts = [x["script"] for x in c2h_state["commits_active"]]
    db_c2h_scripts = [x["script"] for x in db_c2h_state["commits_active"]]
    assert c2h_scripts == db_c2h_scripts

    h2c_scripts = [x["script"] for x in h2c_state["commits_active"]]
    db_h2c_scripts = [x["script"] for x in db_h2c_state["commits_active"]]
    assert h2c_scripts == db_h2c_scripts
Esempio n. 13
0
def _assert_states_synced(handle, c2h_state, h2c_state):
    connection = db.hub_connection(handle=handle)
    db_h2c_state = db.load_channel_state(connection["h2c_channel_id"],
                                         connection["asset"])
    db_c2h_state = db.load_channel_state(connection["c2h_channel_id"],
                                         connection["asset"])
    assert c2h_state["asset"] == db_c2h_state["asset"]
    assert h2c_state["asset"] == db_h2c_state["asset"]
    assert c2h_state["deposit_script"] == db_c2h_state["deposit_script"]
    assert h2c_state["deposit_script"] == db_h2c_state["deposit_script"]

    c2h_scripts = [x["script"] for x in c2h_state["commits_active"]]
    db_c2h_scripts = [x["script"] for x in db_c2h_state["commits_active"]]
    assert c2h_scripts == db_c2h_scripts

    h2c_scripts = [x["script"] for x in h2c_state["commits_active"]]
    db_h2c_scripts = [x["script"] for x in db_h2c_state["commits_active"]]
    assert h2c_scripts == db_h2c_scripts
Esempio n. 14
0
def get_status(hub_conn, clearance=6, cursor=None):
    from picopayments_hub import api

    send_state = db.load_channel_state(
        hub_conn["h2c_channel_id"], hub_conn["asset"], cursor=cursor
    )
    recv_state = db.load_channel_state(
        hub_conn["c2h_channel_id"], hub_conn["asset"], cursor=cursor
    )
    status = Mpc(api).full_duplex_channel_status(
        hub_conn["handle"], etc.netcode, send_state,
        recv_state, get_secret, clearance=clearance
    )
    return {
        "asset": status["asset"],
        "balance": status["balance"],
        "ttl": status["ttl"],
        "status": status["status"]
    }
Esempio n. 15
0
def get_status(hub_conn, clearance=6, cursor=None):
    from picopayments_hub import api

    send_state = db.load_channel_state(hub_conn["h2c_channel_id"],
                                       hub_conn["asset"],
                                       cursor=cursor)
    recv_state = db.load_channel_state(hub_conn["c2h_channel_id"],
                                       hub_conn["asset"],
                                       cursor=cursor)
    status = Mpc(api).full_duplex_channel_status(hub_conn["handle"],
                                                 etc.netcode,
                                                 send_state,
                                                 recv_state,
                                                 get_secret,
                                                 clearance=clearance)
    return {
        "asset": status["asset"],
        "balance": status["balance"],
        "ttl": status["ttl"],
        "status": status["status"]
    }
Esempio n. 16
0
def load_connection_data(handle,
                         new_c2h_commit=None,
                         new_h2c_revokes=None,
                         cursor=None):
    from picopayments_hub import api
    # FIXME this is getting dangerous, used in lib and verify, split it up!

    # connection data
    connection = db.hub_connection(handle=handle, cursor=cursor)
    if not connection:
        raise err.HandleNotFound(handle)
    asset = connection["asset"]
    terms = db.terms(id=connection["terms_id"], cursor=cursor)

    # h2c data
    h2c_state = db.load_channel_state(connection["h2c_channel_id"],
                                      connection["asset"],
                                      cursor=cursor)
    if new_h2c_revokes is not None:
        h2c_state = api.mpc_revoke_all(state=h2c_state,
                                       secrets=new_h2c_revokes)
    h2c_deposit_address = deposit_address(h2c_state)
    h2c_transferred = get_transferred_quantity(h2c_state)
    h2c_deposit = get_balances(h2c_deposit_address, [asset])[asset]
    h2c_unnotified_commit = db.unnotified_commit(
        channel_id=connection["h2c_channel_id"], cursor=cursor)

    # c2h data
    c2h_state = db.load_channel_state(connection["c2h_channel_id"],
                                      connection["asset"],
                                      cursor=cursor)
    if new_c2h_commit is not None:
        c2h_state = api.mpc_add_commit(state=c2h_state,
                                       commit_rawtx=new_c2h_commit["rawtx"],
                                       commit_script=new_c2h_commit["script"])
    c2h_transferred = get_transferred_quantity(c2h_state)

    # payments
    send_payments_sum = db.send_payments_sum(handle=handle, cursor=cursor)
    recv_payments_sum = db.recv_payments_sum(handle=handle, cursor=cursor)
    payments_sum = recv_payments_sum - send_payments_sum

    # sendable (what this channel can send to another)
    sendable_amount = c2h_transferred + payments_sum - h2c_transferred
    assert sendable_amount >= 0

    # receivable (what this channel can receive from another)
    receivable_amount = h2c_deposit + c2h_transferred - payments_sum
    assert receivable_amount >= 0

    return {
        "connection": connection,
        "h2c_state": h2c_state,
        "h2c_expired": is_expired(h2c_state, etc.expire_clearance),
        "c2h_state": c2h_state,
        "c2h_expired": is_expired(c2h_state, etc.expire_clearance),
        "h2c_unnotified_commit": h2c_unnotified_commit,  # FIXME remove
        "sendable_amount": sendable_amount,
        "receivable_amount": receivable_amount,
        "terms": terms,
    }
Esempio n. 17
0
def load_connection_data(handle, new_c2h_commit=None,
                         new_h2c_revokes=None, cursor=None):
    from picopayments_hub import api
    # TODO this is getting dangerous, used in lib and verify, split it up!

    # connection data
    connection = db.hub_connection(handle=handle, cursor=cursor)
    if not connection:
        raise err.HandleNotFound(handle)
    asset = connection["asset"]
    terms = db.terms(id=connection["terms_id"], cursor=cursor)

    # h2c data
    h2c_state = db.load_channel_state(
        connection["h2c_channel_id"], connection["asset"], cursor=cursor
    )
    if new_h2c_revokes is not None:
        h2c_state = api.mpc_revoke_all(state=h2c_state,
                                       secrets=new_h2c_revokes)
    h2c_deposit_address = deposit_address(h2c_state)
    h2c_transferred = get_transferred_quantity(h2c_state)
    h2c_deposit = get_balances(h2c_deposit_address, [asset])[asset]

    # TODO remove now impossable unnotified commit?
    h2c_unnotified_commit = db.unnotified_commit(
        channel_id=connection["h2c_channel_id"], cursor=cursor
    )

    # c2h data
    c2h_state = db.load_channel_state(
        connection["c2h_channel_id"], connection["asset"], cursor=cursor
    )
    if new_c2h_commit is not None:
        c2h_state = api.mpc_add_commit(
            state=c2h_state,
            commit_rawtx=new_c2h_commit["rawtx"],
            commit_script=new_c2h_commit["script"]
        )
    c2h_transferred = get_transferred_quantity(c2h_state)

    # payments
    send_payments_sum = db.send_payments_sum(handle=handle, cursor=cursor)
    recv_payments_sum = db.recv_payments_sum(handle=handle, cursor=cursor)
    payments_sum = recv_payments_sum - send_payments_sum

    # sendable (what this channel can send to another)
    sendable_amount = c2h_transferred + payments_sum - h2c_transferred
    assert sendable_amount >= 0

    # receivable (what this channel can receive from another)
    receivable_amount = h2c_deposit + c2h_transferred - payments_sum
    assert receivable_amount >= 0

    return {
        "connection": connection,
        "h2c_state": h2c_state,
        "h2c_expired": is_expired(h2c_state, etc.expire_clearance),
        "c2h_state": c2h_state,
        "c2h_expired": is_expired(c2h_state, etc.expire_clearance),
        "h2c_unnotified_commit": h2c_unnotified_commit,
        "sendable_amount": sendable_amount,
        "receivable_amount": receivable_amount,
        "terms": terms,
    }