Skip to content

Instantly share code, notes, and snippets.

@antiochp
Last active May 23, 2019 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antiochp/dfb1cb0273421584a1fcf2341da9d683 to your computer and use it in GitHub Desktop.
Save antiochp/dfb1cb0273421584a1fcf2341da9d683 to your computer and use it in GitHub Desktop.

Superseded by the approach described here - https://gist.github.com/antiochp/d490280fa0b87e0cd84f961b0911119f


Elder Channels

Elder (inspired by Eltoo).

Also "Elder Wand".


Taking heavy insiration from the Eltoo Channel design but leveraging the simplicity (and contraints) of Grin/MW transactions.

Unlike Eltoo we do not have the ability to "rebind" transactions based on output scripts (there are no scripts in Grin/MW).

In contrast we can easily build on unconfirmed transactions. We can also aggregate and cut-through intermediate state. Rebinding becomes multi-kernel tx aggregation.

Fees are ignored here for simplicity (and potentially ignored by design).

tl;dr Elder payment channels are simple aggregated cut-through transactions involving multisig outputs and some careful constraints around tx dependencies.

Channel State

Channel state is represented as a single multisig output.

Outchannel_0

The channel is initially funded by building this multisig output. Alice and Bob build this transaction, sending funds to it as necessary.

Txfund_0 ([InA, InB] -> Outchannel_0, Kernfund_0)

The channel is open once this funding tx is broadcast and confirmed.

Updating the channel state is done via a series of "update" txs spending from the previous multisig output to a new multisig output.

Txupdate_1 (Inchannel_0 -> Outchannel_1, Kernupdate_1)

Multiple updates txs can be combined into a single aggregate tx. The resulting cut-through tx consists of a spend from the initial state to the latest state and kernels for each intermediate state update.

Txupdate_1 (Inchannel_0 -> Outchannel_1, Kernupdate_1)

Txupdate_2 (Inchannel_1 -> Outchannel_2, Kernupdate_2)

Txupdate_3 (Inchannel_2 -> Outchannel_3, Kernupdate_3)

=> Txupdate_1_3 (Inchannel_0 -> Outchannel_3, [Kernupdate_1, Kernupdate_2, Kernupdate_3])

These updates are built but not broadcast. They only need to be broadcast to close the channel non-cooperatively.

Alice and Bob both store the latest channel state and all previous update kernels. They do not need to store all previous update transactions.

Skip Lists

Both parties need to mutually agree on state updates. Normally they would build individual transactions to transition from one state to another. Channels may have large numbers of state transitions and funds are adjusted between parties. It would be relatively easy for both parties to build additional "skip list" transactions to keep the number of kernels manageable.

[Needs more thought but additional txs like (Inchannel_0 -> Outchannel_10) allowing sequences of update txs to be skipped.]

Cooperative Close

The "cooperative close" scenario is straightforward. Both parties can mutually decide to close the channel at any point by simply spending the multisig output, sending funds to both parties as desired.

Txcoop_0 (Inchannel_0 -> [OutA, OutB], Kerncoop_0)

A cooperatively closed Elder channel is indistinguishable from a couple of regular transactions ("fund" and "close"). The outputs are not even identifiable as multisig.

Non-Cooperative Close

The "non-cooperative close" scenario allows either party to close the current channel state. This is done by mutually building a pair of "close" and "settle" txs for each channel state.

The "close" and "settle" transactions must be mutually negotiated by the parties involved in the channel and should be built prior to the corresponding state "update" tx to avoid funds getting blocked by either party.

These are symmetric transaction (as in Eltoo Channels). Neither Alice nor Bob require endpoint specific transactions.

Txupdate_2 (Inchannel_1 -> Outchannel_2, Kernupdate_2)

Each channel state has a corresponding pair of "close" and "settle" transactions associated with it. The "settle" transaction has a relative lock height against the "close" transaction. This lock height is used to "revoke" a "close" and is described later. The example here uses a relative lock height of 1,440 (24 hours). The "settle" tx can only be accepted 1,440 blocks after the "close" tx has been confirmed. This 24 hour delay can be avoided by cooperatively closing the channel.

Txclose_2 (Inchannel_2 -> Outclose_2, Kernclose_2)

Txsettle_2 (Inclose_2 -> [OutA, OutB], Kernsettle_2,close_2,1440)

Either party can close the channel at the latest state via the appropriate "close" tx. Each channel state has an associated close tx. Only the latest state should be closed. The problem here is any old state can be closed using an old "close" tx. We prevent this by allowing the other party to "revoke" a previous "close" tx.

Preventing Close of Old State - Revoke

The "revoke" tx spends a specific "close" output back to the next successive channel state. If either party sees a previous "close" they can immediately "revoke" it and begin the process of a non-cooperative close on the latest state.

Txrevoke_0 (Inclose_0 -> Outchannel_1, Kernrevoke_1)

Each state can potentially be the result of a state "update" or from the "revoke" of a "close" on the previous state.

For example if we are currently at state Outchannel_2 and Alice sees an old state close attempt (Outclose_0) they can broadcast the necessary "revoke" and subsequent channel state update txs to close the most recent state. These can be aggregated and cut-through to produce a single aggregated "revoke and close" tx.

The resulting cut-through tx consists of a spend of the old revoked "close" output to the latest close state along with all intermediate channel state update kernels.

Txrevoke_0 (Inclose_0 -> Outchannel_1, Kernrevoke_1)

Txupdate_2 (Inchannel_1 -> Outchannel_2, Kernupdate_2)

Txclose_2 (Inchannel_2 -> Outclose_2, Kernclose_2)

=> Txrevoke_close (Inclose_0 -> Outclose_2, [Kernrevoke_1, Kernupdate_2, Kernclose_2])

Either party can revoke any close attempt on an old previous state. There is no need to penalize the other party as we can simply close the channel in the non-cooperative case if this occurs.

We take advantage of the delay between "close" and "settle" here to "revoke" as necessary. A "revoke" of an old state can always be broadcast before the revoked "settle" tx.

A "revoked" channel collapses into a non-cooperatively closed channel. There is nothing on-chain to distinguish these two cases. The relative lock height between the "close" and "settle" transactions will be visible on-chain but these will be indistinguishable from any other relative lock height occurence.

Local Storage Requirements

Either party can close the latest state from any prior state. To do so they need the latest "close" and "settle" tx pair and the kernels (but not the txs themselves) from all previous state updates.

Txclose_n (Inchannel_n -> Outclose_n, Kernclose_n)

Txsettle_n (Inclose_n -> [OutA, OutB], Kernsettle_n,close_n,1440)

[Kernupdate_1, Kernupdate_2, ..., Kernupdate_n]

To support revocation of any previous close state they also need to store all previous "revoke" kernels.

[Kernrevoke_0, Kernrevoke_1, ..., Kernrevoke_n-1]

Regardless of what either party broadcasts, the other party can construct a single compensating tx that will robustly close the channel at the latest state from the lateast state and the set of previous kernels.

Transaction Weight Limits

We can reduce the total number of kernels in the aggregate tx (see "skip lists") but we will still likely be limited in terms of how many state updates we can robuystly perform on a given channel. We will likely have to consider some kind of channel "refresh" or "reset" transaction, similar to the initial funding transaction that will allow the parties to mutually refresh or reset the channel over time.

1,000 channel updates will require a relatively large tx (1,000 intermediate kernels) to close it non-cooperatively for exmple. This is still significantly smaller than the full 1,0000 intermediate transactions.

Transaction Fees

We have ignored fees entirely in the above description. We can always increase the fees of any given tx (smaller than max weight) by aggregating it with a high fee tx. The entire channel construction may be workable by deferring the introduction of fees until the channel is closed.

Any party attempting to close an old state will need to provide the necessary fees.

Any party deciding to non-cooperatively close the channel will need to provide sufficient fees to do so.

If the channel is cooperatively closed the parties can mutually negotiate a suitable fee.

@tromp
Copy link

tromp commented May 20, 2019

I don't like the chaining of txs and resultant accumulation of kernels, which the skip lists only partially address.
A direct implementation of Poon-Dryja channels [1] would only require only a handful of kernels to be broadcast in total?!

[1] https://lightning.network/lightning-network-paper.pdf

@casey
Copy link

casey commented May 20, 2019

My hunch is that the accumulation of kernels will lead to strange states that are hard to reason about. I.e. channels which cannot be non-cooperatively closed because the number of accumulated kernels is prohibitive.

@antiochp
Copy link
Author

Hmm ok. Sounds like the ever growing list of intermediate kernels is not a popular option. I was hoping this would be manageable via careful building of txs throughout the channel lifecycle.

A direct implementation of Poon-Dryja channels

This requires a large amount of state overhead for each party involved in the channel. Specifically full transactions for Commitment, Delivery, Revocable Delivery and Breach Remedy for each channel state update. At least some of these must be retained by each party for the full duration of the channel.
Some of these are also very sensitive and must be kept confidential as they will guarantee loss of all funds on the channel if any other party has access to them. If revoked state attributable to Alice is ever published, Bob can claim all funds on the channel.

There is a lot of complexity involved in Poon-Dryja channels that I was hoping to avoid by leveraging MW transaction semantics.

I think I may have an alternative construction that looks a bit like Poon-Dryja and a bit like Eltoo actually - will post another gist shortly.

@tromp
Copy link

tromp commented May 21, 2019

Yes; for each new payment in the channel, we would need to create

2 new musig outputs
4 new plain outputs

2 single-input, 2-output transactions (Commitment)
2 single input, single output, relative timelocked transactions (Revocable Delivery)
2 single input, single output transactions (Breach Remedy)

Having to keep your Remedies around is indeed a downside, but if cost of storage becomes an issue,
perhaps after thousands of channel payments, then one could consider closing and re-opening the channel to reclaim that storage.

The above assumes we allow duplicate output commitments. Without those, we would need an additional 2 musig outputs and 2 transactions.

@antiochp
Copy link
Author

@tromp https://gist.github.com/antiochp/d490280fa0b87e0cd84f961b0911119f

Uses a single multisig output for channel state.
Channel state updates are new versions of the close and settle txs.
Other party can revoke an old close (similar to a "remedy" but without the nuclear penalty).
Txs need to be attributable (endpoint specific).
No chains of tx kernels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment