.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/structures/chain/plot_01_payment_lifecycle.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_structures_chain_plot_01_payment_lifecycle.py: One payment from signature to reorganization and reinclusion ============================================================ This capstone follows one payment through every subpackage: Alice signs it, peers gossip it into their pending queues (mempools), a miner includes it in a block, a competing branch with more work displaces that block, and the payment is included again. :doc:`/tutorials/life_of_a_payment` tells the same story step by step. Bob's balance goes from 0 to 25, back to 0 when the competing branch wins, and to 25 again after reinclusion. Queueing a payment never changes a balance, and the signature stays valid throughout. The scenario is deliberately small: one pending payment per peer, no fees, and a shared in-memory registry that resolves announced hashes, with parents sent before children. .. GENERATED FROM PYTHON SOURCE LINES 20-24 .. code-block:: Python import matplotlib.pyplot as plt import blockchainkit as bk .. GENERATED FROM PYTHON SOURCE LINES 25-27 Sign and validate before queueing --------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 27-83 .. code-block:: Python alice_key = 7 alice_public = bk.crypto.public_key(alice_key) alice = bk.structures.address(alice_public) bob = bk.structures.address(bk.crypto.public_key(11)) initial = bk.structures.Ledger({alice: 100}) genesis = bk.consensus.mine(bk.structures.Block(difficulty=4)).block peers = {name: bk.structures.Blockchain(genesis, initial) for name in ("west", "east")} queues = {name: {} for name in peers} payment = bk.structures.Transaction(alice_public, bob, 25, 0).signed(alice_key, signing_nonce=17) assert payment.is_valid() # apply returns a new snapshot. Discarding it checks validity without spending. assert initial.apply([payment]).balances[bob] == 25 assert initial.balances.get(bob, 0) == 0 registry = {payment.txid: payment} events = [] def record(stage): events.append( [ stage, *[peers[n].state.balances.get(bob, 0) for n in peers], *[len(queues[n]) for n in peers], ] ) def receive(delivery): item = registry[delivery.payload] peer = peers[delivery.recipient] if isinstance(item, bk.structures.Transaction): peer.state.apply([item]) queues[delivery.recipient][item.txid] = item else: peer.add(item) # Reconcile our one-payment queue against the new canonical state. # After a reorganization an old payment may become eligible again. try: peer.state.apply([payment]) except ValueError: queues[delivery.recipient].pop(payment.txid, None) else: queues[delivery.recipient][payment.txid] = payment network = bk.network.SimulatedNetwork(peers, on_receive=receive) network.connect("west", "east", latency=(2, 2)) network.broadcast("west", payment.txid) record("Queued at west") assert len(queues["west"]) == 1 and not queues["east"] network.run() record("Payment propagated") assert all(len(queue) == 1 for queue in queues.values()) assert all(peer.state.balances.get(bob, 0) == 0 for peer in peers.values()) .. GENERATED FROM PYTHON SOURCE LINES 84-86 Partition and mine competing histories -------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 86-99 .. code-block:: Python network.disconnect("west", "east") paid = bk.consensus.mine( bk.structures.Block(genesis.hash, tuple(queues["west"].values()), 1, 3, 4) ).block alternate = bk.consensus.mine(bk.structures.Block(genesis.hash, (), 1, 4, 4)).block extension = bk.consensus.mine(bk.structures.Block(alternate.hash, (), 2, 5, 4)).block for block, sender in [(paid, "west"), (alternate, "east"), (extension, "east")]: registry[block.hash] = block network.broadcast(sender, block.hash) record("Partition: west includes payment") assert peers["west"].state.balances[bob] == 25 assert not queues["west"] and len(queues["east"]) == 1 .. GENERATED FROM PYTHON SOURCE LINES 100-102 Reconnect, synchronize, and restore a displaced payment ------------------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 102-115 .. code-block:: Python network.connect("west", "east") network.broadcast("east", alternate.hash) network.run() network.broadcast("east", extension.hash) network.run() record("Longer fork wins; payment pending") assert peers["west"].tip == peers["east"].tip == extension assert all(peer.state.balances.get(bob, 0) == 0 for peer in peers.values()) assert all(payment.txid in queue for queue in queues.values()) proof = bk.structures.MerkleTree([payment.to_bytes()]).proof(0) assert bk.structures.verify_proof(payment.to_bytes(), proof, paid.merkle_root) assert payment.is_valid() .. GENERATED FROM PYTHON SOURCE LINES 116-118 Reinclude once; reject replay ----------------------------- .. GENERATED FROM PYTHON SOURCE LINES 118-135 .. code-block:: Python reincluded = bk.consensus.mine( bk.structures.Block(extension.hash, tuple(queues["west"].values()), 3, 6, 4) ).block registry[reincluded.hash] = reincluded network.broadcast("west", reincluded.hash) network.run() record("Reincluded on winning history") assert all(peer.state.balances[bob] == 25 for peer in peers.values()) assert all(not queue for queue in queues.values()) for peer in peers.values(): try: peer.state.apply([payment]) except ValueError as error: assert "nonce" in str(error) else: raise AssertionError("replay must be rejected") .. GENERATED FROM PYTHON SOURCE LINES 136-138 Read the lifecycle as a balance and queue trace ----------------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 138-160 .. code-block:: Python fig, (ax, table_ax) = plt.subplots(2, 1, figsize=(11, 7), height_ratios=[1, 1.2]) for column, peer in [(1, "west"), (2, "east")]: ax.step(range(len(events)), [row[column] for row in events], where="post", label=peer) ax.set(xticks=range(len(events)), xlabel="Stage number (table order)", ylabel="Bob's balance") ax.legend() table_ax.axis("off") table = table_ax.table( cellText=events, colLabels=["Stage", "West balance", "East balance", "West queue", "East queue"], colWidths=[0.48, 0.13, 0.13, 0.13, 0.13], loc="center", ) table.auto_set_font_size(False) table.set_fontsize(9) table.scale(1, 2) fig.suptitle("Signed does not mean pending; included does not mean final") fig.tight_layout() for event in events: print(event) plt.show() .. image-sg:: /api/gallery/structures/chain/images/sphx_glr_plot_01_payment_lifecycle_001.png :alt: Signed does not mean pending; included does not mean final :srcset: /api/gallery/structures/chain/images/sphx_glr_plot_01_payment_lifecycle_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none ['Queued at west', 0, 0, 1, 0] ['Payment propagated', 0, 0, 1, 1] ['Partition: west includes payment', 25, 0, 0, 1] ['Longer fork wins; payment pending', 0, 0, 1, 1] ['Reincluded on winning history', 25, 25, 0, 0] .. GENERATED FROM PYTHON SOURCE LINES 161-169 Exercise -------- Replace an empty competing block with a conflicting payment that spends all of Alice's funds. Predict whether the original payment returns to the queue. Explain why the single-payment queue is insufficient for dependent payments with consecutive account nonces. A worked solution is in :doc:`/exercises/structures`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.206 seconds) .. _sphx_glr_download_api_gallery_structures_chain_plot_01_payment_lifecycle.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/structures/chain/plot_01_payment_lifecycle.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_01_payment_lifecycle.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_01_payment_lifecycle.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_01_payment_lifecycle.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_