Plasma: child chains secured by exit games (Poon and Buterin, 2017)#

Plasma runs a whole chain off chain. An operator produces its blocks and posts only their Merkle roots to a root-chain contract, which cannot check the transactions behind them. Coins stay safe because their owners can always exit: prove ownership on the root chain, then wait out a challenge period during which anyone can prove the exit wrong.

In Plasma Cash every coin has its own slot in each block’s sparse Merkle tree. An exit shows the coin’s last transfer and the one before it, and is cancelled by a later transfer signed by the exiting owner (it spent the coin) or by a different transfer signed by the previous owner in between (the exiting transfer was a double spend). An exit stands only if

\[\text{no valid challenge arrives within the period}.\]
import matplotlib.pyplot as plt

import blockchainkit as bk
from blockchainkit.crypto import public_key

ALICE, BOB, CAROL, DAVE = 7, 5, 11, 13

A coin changes hands three times#

chain = bk.channels.PlasmaChain(period=7)
deposit = chain.deposit(public_key(ALICE))  # Block 1.
to_bob = chain.submit_block([bk.channels.sign_transfer(ALICE, 0, public_key(BOB), 1)])
to_carol = chain.submit_block([bk.channels.sign_transfer(BOB, 0, public_key(CAROL), to_bob)])

timeline = []
# Bob tries to exit the coin he already gave Carol.
stale = chain.start_exit(chain.prove(0, to_bob), deposit, height=10)
assert chain.challenge(stale, chain.prove(0, to_carol), height=12)
timeline.append(("Bob exits a spent coin", 10, 12, "challenged"))

# Alice signs the coin away a second time; a colluding operator includes it.
to_dave = chain.submit_block([bk.channels.sign_transfer(ALICE, 0, public_key(DAVE), 1)])
forged = chain.start_exit(chain.prove(0, to_dave), deposit, height=20)
assert chain.challenge(forged, chain.prove(0, to_bob), height=23)
timeline.append(("Dave exits a double spend", 20, 23, "challenged"))

# Carol, the rightful owner, exits unchallenged.
rightful = chain.start_exit(chain.prove(0, to_carol), chain.prove(0, to_bob), height=30)
assert not chain.challenge(rightful, chain.prove(0, to_dave), height=31)
assert chain.finalize(rightful, height=37).owner == public_key(CAROL)
timeline.append(("Carol exits her coin", 30, 37, "finalized"))
print([status for *_, status in timeline])
['challenged', 'challenged', 'finalized']

Why a withholding operator forces mass exits#

If the operator stops publishing blocks, each owner must exit each coin on the root chain, within the challenge period.

coins = [1, 10, 100, 1_000, 10_000]
exits_per_block = 50  # What the root chain can absorb per block, say.
blocks_needed = [c / exits_per_block for c in coins]

fig, (left, right) = plt.subplots(1, 2, figsize=(11, 4))
for row, (_, start, end, status) in enumerate(timeline):
    color = "#16a34a" if status == "finalized" else "#dc2626"
    left.barh(row, end - start, left=start, color=color)
    left.text(end + 0.5, row, status, va="center")
left.set_yticks(range(len(timeline)), labels=[label for label, *_ in timeline])
left.set(xlabel="root-chain height", xlim=(0, 50), title="Three exits of one coin")
right.loglog(coins, blocks_needed, "o-", color="#d97706")
right.axhline(7, color="black", linestyle="--", label="challenge period")
right.set(xlabel="coins on the child chain", ylabel="root-chain blocks to exit them all")
right.set_title("Mass exit after the operator disappears")
right.legend()
fig.tight_layout()

plt.show()
Three exits of one coin, Mass exit after the operator disappears

Exercise#

Plasma Cash also lets a challenger accuse an exit of an invalid history, which the exiting owner answers with a later transfer. Sketch that third challenge on PlasmaChain: what must the challenger show, and what must the owner answer with?

Total running time of the script: (0 minutes 0.213 seconds)

Gallery generated by Sphinx-Gallery