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
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
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
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
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)
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 )
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"])
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)
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
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
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
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"] }
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"] }
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, }
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, }