Implementing Collateralized Channels on Ethereum using Go-Perun

Perun Network
PolyCrypt
Published in
5 min readMar 22, 2021

Previously, we introduced collateralized channels and discussed how they can enable peer-to-peer payments in an offline setting.

Collateralized channels can be a solution to digital payments in an offline setting. (Icons: flaticon.com)

In the following we show how collateralized channels on Ethereum are implemented using the Go-Perun library. We start with a brief introduction to Go-Perun and then go through the main aspects of the implementation.

Go-Perun in a nutshell.

Go-perun is an open-source library for building state channel applications on top of almost any transaction backend.

Interface abstraction. At the core of the library is the client package which represents the fundamental protocols. A client interacts with its environment through abstract interfaces.

  • The transaction backend is represented by package wallet, which represents account logic and package channel, which provides the core data structures of a channel.
  • The peer-to-peer communication layer is represented by package wire.
  • Data persistence is provided through package channel/persistence.
  • Logging is provided through the package log.
The go-perun architecture.

Instantiation depending on the application context. When building an application, the abstract interfaces are instantiated with concrete implementations depending on the application context. For the purpose of this article, we use the Ethereum blockchain as the transaction backend and a simple TCP/IP adapter for client communication. We will not care so much about persistence and logging here, but the library ships with a basic LevelDB adapter for data persistence and a Logrus adapter for logging.

For a more detailed introduction to building applications using go-perun we recommend the go-perun developer tutorial.

Perun channels.

A channel in go-perun is an object that allows two parties of a larger network to transact peer-to-peer without going through the global transaction backend (e.g., the Ethereum blockchain) every time. A channel is typically prefunded at the beginning, and settled at the end, via the global backend.

Channel state. The state of a channel in go-perun consists of a channel identifier, a version counter, a channel outcome, a byte string of application data, and a boolean flag indicating whether the channel has been finalized. The channel identifier uniquely identifies the channel and is derived from a set of parameters including the set of participants, the app definition, and a random nonce. The version counter is used to identify the most recent state. The outcome holds the channel balances of each participant, which can be distributed across several asset types. The appData can hold arbitrary data associated with the channel application. The isFinal flag defines whether the channel has been finalized. A finalized channel can be settled much quicker.

Data structure of a Perun channel state in Solidity.

Channel operation. When opening a channel in go-perun, the balances in the outcome are set to the expected channel funding of each participant. Clients will only accept transactions once the corresponding assets have been deposited into the channel. When transacting on a channel, the clients mutually agree on updating the channel state. At settlement, the backend will redistribute the channel assets according to the balances defined in the outcome of the state with the highest version number.

Channel applications. In order to implement collateralized channels in go-perun, we customize the standard channel functionality via a channel application. A channel application is a smart contract that runs on the global backend and is referenced in the channel parameters. A channel application allows us to customize the rules according to which transactions on the corresponding channel can be performed.

Implementing collateralized channels

A collateralized channel can be thought of as a special type of channel with a customized funding and settlement logic. While standard channels allow transactions only once they are funded, collateralized channels must work without pre-funding. Crucially, collateralized channels must be able to detect missing funds at settlement and take the missing funds from the referenced collateral pools.

Encoding balances as app data. For realizing a collateralized channel, we use an unfunded channel that has all-zero balances in the outcome field. Hence, there is no funding transaction necessary on channel opening. Instead of tracking the channel balances in the outcome field, we track them in the appData field. We will use a signed integer encoding where a negative balance means that the client has a debit and a positive balance means that the client has a credit in the channel.

OnConclude callback. We need to ensure that at channel settlement the assets are distributed according to the concluded client credit and debit balances. We inject our custom settlement logic via an `onConclude` callback in the channel application, which is called whenever a collateralized channel is concluded.

Attention: The `onConclude` callback is made available through customization of the Perun smart contracts. It should not be used in production without excessive testing.

The customized settlement logic is injected via an onConclude callback.

Settlement logic. The settlement logic is implemented in a special asset holder contract that also manages collateral pools. If a client has negative balances at settlement and does not have sufficient assets funded into the channel, the contract attempts to take the missing assets from the client’s collateral pool. If this fails, the contract emits a global event on the backend which signals that the client has failed to pay his debts.

Core of the customized settlement logic.

Interacting with collateralized channels.

At the core of the go-perun library, collateralized channels are treated as unfunded channels. This means that the responsibility of overseeing sufficient funding is shifted from the library to the user.

Payment acceptance policy. In our example implementation, we let the user accept payment transactions via a payment acceptance policy. At each incoming transaction, the client will be informed about the payment amount, the current balances of the payment sender, the funding, and the collateral. In our example, our client accepts all pre-funded payments. Unfunded payments are only accepted if the payer has never overdrawn its collateral and the unfunded payment is at most 10% of the collateral size.

Payment acceptance policy of Client2.

Source code & more information.

The full source code of the example above can be found at GitHub. The go-perun library is open source and also available at GitHub. More information on the Perun project can be found at https://perun.network.

Thanks for reading!

Stay tuned for more exciting posts on off-chain solutions. You can also follow us on LinkedIn and Twitter for more information about our recent activities.

--

--