.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/channels/payment_channels/plot_06_pisa_watchtowers.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_channels_payment_channels_plot_06_pisa_watchtowers.py: Watchtowers and PISA: guarding offline channel parties (McCorry et al., 2019) ============================================================================= A Lightning party is safe only while it watches the chain: if it is offline for longer than the channel's delay, a revoked commitment can be swept. A *watchtower* watches for it. After each update the party gives the tower a hint, half the revoked commitment's txid, and a presigned penalty encrypted under the whole txid; the tower learns nothing until that commitment appears, and can then publish only a penalty that pays the party. PISA made towers accountable: the tower signs a receipt for every appointment and locks a deposit, which the party claims if a revoked state it was hired to watch gets swept. For a party that checks the chain every ``T`` blocks, against a breach at a random moment and a delay ``d``, .. math:: P(\text{loss}) = \max\left(0,\ 1 - \frac{d}{T}\right) \times P(\text{tower fails}). .. GENERATED FROM PYTHON SOURCE LINES 23-29 .. code-block:: Python from random import Random import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 30-32 A tower penalizes while Bob is offline -------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 32-49 .. code-block:: Python def breach(tower_online): channel = bk.channels.LightningChannel(("alice", "bob"), (7, 5), (100, 0), delay=144) channel.pay("alice", 60) tower = bk.channels.Watchtower(99, collateral=500, online=tower_online) hint, blob = bk.channels.make_appointment(channel, "bob", state=0) receipt = tower.appoint(hint, blob, expires=10_000) channel.publish("alice", state=0, height=1_000) # Alice tries her best old state. return channel, tower, receipt channel, tower, receipt = breach(tower_online=True) settlement = tower.watch(channel, height=1_001) print("with a tower:", dict(settlement.payouts)) assert settlement.kind == "penalty" and settlement.payouts["bob"] == 100 .. rst-class:: sphx-glr-script-out .. code-block:: none with a tower: {'alice': 0, 'bob': 100} .. GENERATED FROM PYTHON SOURCE LINES 50-52 A tower that fails pays under PISA ---------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 52-59 .. code-block:: Python channel, lazy, receipt = breach(tower_online=False) assert lazy.watch(channel, height=1_001) is None channel.sweep(height=1_144) assert bk.channels.verify_receipt(receipt) and bk.channels.tower_liable(receipt, channel) print(f"the tower forfeits its {lazy.collateral} collateral to Bob") .. rst-class:: sphx-glr-script-out .. code-block:: none the tower forfeits its 500 collateral to Bob .. GENERATED FROM PYTHON SOURCE LINES 60-64 How often a breach succeeds --------------------------- Bob checks every T blocks; the breach happens at a uniformly random moment; the tower fails 5% of the time. .. GENERATED FROM PYTHON SOURCE LINES 64-88 .. code-block:: Python delay, tower_failure = 144, 0.05 intervals = [72, 144, 288, 576, 1_008, 2_016] rng = Random(8) simulated = [] for interval in intervals: trials = 20_000 missed = sum(rng.uniform(0, interval) > delay for _ in range(trials)) simulated.append(missed / trials) alone = [max(0.0, 1 - delay / t) for t in intervals] assert all(abs(s - a) < 0.02 for s, a in zip(simulated, alone, strict=True)) fig, ax = plt.subplots(figsize=(8, 4)) days = [t / 144 for t in intervals] ax.plot(days, alone, "o-", color="#dc2626", label="Bob alone") ax.plot(days, simulated, "x", color="black", label="simulated") ax.plot(days, [a * tower_failure for a in alone], "s-", color="#16a34a", label="with a tower") ax.set(xlabel="days between Bob's checks (delay = 1 day)", ylabel="chance a breach succeeds") ax.set_title("A watchtower covers the time Bob is away") ax.legend() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/channels/payment_channels/images/sphx_glr_plot_06_pisa_watchtowers_001.png :alt: A watchtower covers the time Bob is away :srcset: /api/gallery/channels/payment_channels/images/sphx_glr_plot_06_pisa_watchtowers_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 89-95 Exercise -------- A tower that is paid per appointment and never acts earns money for nothing. With PISA's collateral of ``C`` and a breach probability ``b`` per appointment, what collateral makes acting worthwhile for a tower whose cost of watching is ``w`` per appointment? .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.341 seconds) .. _sphx_glr_download_api_gallery_channels_payment_channels_plot_06_pisa_watchtowers.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/channels/payment_channels/plot_06_pisa_watchtowers.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_06_pisa_watchtowers.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_06_pisa_watchtowers.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_06_pisa_watchtowers.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_