Migrating from Ethereum to Polkadot

In order to make our payment channel implementation work on Polkadot we utilize the perun-polkadot-backend. Most changes are done in the PaymentClient and its setup. The actual PaymentChannel implementation stays the same.

Most notably, we don’t need to deploy smart contracts like the Adjudicator and AssetHolder anymore. All necessary contracts are provided by the perun-polkadot-pallet which is deployed on the perun-polkadot-node we are using as our local test chain.

Note

Pallets are modules that act as building blocks for constructing unique blockchains. Each pallet contains domain-specific logic. The Perun Polkadot Pallet provides go-perun state channels for all Substrate compatible blockchains.

Dependencies

Again, we require Go as described in the payment channel dependencies.

Source Code

This tutorial’s source code is available at perun-examples/payment-channel-dot.

# Download repository.
git clone https://github.com/perun-network/perun-examples.git
cd perun-examples/payment-channel-dot

Docker

For the Polkadot part of the tutorial, we require docker to run our local perun-polkadot-node. You can find installation instructions here.

# Check that docker is installed.
docker -v

Changes

The following modifications take the Ethereum payment channel implementation as a foundation.

Client

Utilities. We make adjustments to client/util.go first. As we are on Polkadot now, we replace the conversion functions EthToWei/WeiToEth with DotToPlanck/PlanckToDot. Also, we drop the CreateContractBackend function.

Constructor. Then we take a look at SetupPaymentClient in client/client.go. We replace CreateContractBackend with dot.NewAPI, which acts as our chain connection given the nodeURL and networkId. Then, we use the resulting api object to create a Pallet from which we derive a new Funder and Adjudicator.

// SetupPaymentClient creates a new payment client.
func SetupPaymentClient(
	bus wire.Bus, // bus is used of off-chain communication.
	w *dotwallet.Wallet, // w is the wallet used to resolve addresses to accounts for channels.
	acc wallet.Account, // acc is the account to be used for signing transactions.
	nodeURL string, // nodeURL is the URL of the blockchain node.
	networkId dot.NetworkID, // networkId is the identifier of the blockchain.
	queryDepth types.BlockNumber, // queryDepth is the number of blocks being evaluated when looking for events.
) (*PaymentClient, error) {
	// Connect to backend.
	api, err := dot.NewAPI(nodeURL, networkId)
	if err != nil {
		panic(err)
	}

	// Connect to Perun pallet and get funder + adjudicator from it.
	perun := pallet.NewPallet(pallet.NewPerunPallet(api), api.Metadata())
	funder := pallet.NewFunder(perun, acc, 3)
	adj := pallet.NewAdjudicator(acc, perun, api, queryDepth)

We set up the dispute watcher and create the perunClient to instantiate the full PaymentClient. Notice that we use the Polkadot specific wallet dotwallet.Wallet and asset dotchannel.Asset here.

	// Setup dispute watcher.
	watcher, err := local.NewWatcher(adj)
	if err != nil {
		return nil, fmt.Errorf("intializing watcher: %w", err)
	}

	// Setup Perun client.
	waddr := dotwallet.AsAddr(acc.Address())
	perunClient, err := client.New(waddr, bus, funder, adj, w, watcher)
	if err != nil {
		return nil, errors.WithMessage(err, "creating client")
	}

	// Create client and start request handler.
	c := &PaymentClient{
		perunClient: perunClient,
		account:     waddr,
		currency:    &dotchannel.Asset,
		channels:    make(chan *PaymentChannel, 1),
	}

	go perunClient.Handle(c, c)
	return c, nil
}

Setup

We apply the following changes to util.go.

General. The deployContracts function is omitted as the Perun Pallet is predeployed at node startup and therefore no contract deployment will be necessary. Also, the balanceLogger is updated to work with Polkadot addresses.

Client setup. The function setupPaymentClient is adapted to suit the new paymentClient constructor. Most notably, we create a Polkadot-specific wallet via dotwallet.NewWallet and adapt the constructor’s parameters.

Run

We slightly adapt the demo scenario in main.go.

Environment. The following constants describe the updated test environment.

const (
	chainURL        = "ws://127.0.0.1:9944"
	networkID       = 42
	blockQueryDepth = 100

	// Private keys.
	keyAlice = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"
	keyBob   = "0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89"
)

Main function. There are only minor adjustments made to the scenario sequence:

  • The contract deployment is removed.

  • We use blockQueryDepth in the setupPaymentClient call. This constant specifies how many blocks an event subscription scans for past events.

Note

On our Polkadot node, Alice and Bob start with 1.153 MDot each. Hence we use a higher balance for funding and payments in main.go.

// main runs a demo of the payment client. It assumes that a blockchain node is
// available at `chainURL` and that the accounts corresponding to the specified
// secret keys are provided with sufficient funds.
func main() {
	// Setup clients.
	log.Println("Setting up clients.")
	bus := wire.NewLocalBus() // Message bus used for off-chain communication.
	alice := setupPaymentClient(bus, chainURL, networkID, blockQueryDepth, keyAlice)
	bob := setupPaymentClient(bus, chainURL, networkID, blockQueryDepth, keyBob)

	// Print balances before transactions.
	l := newBalanceLogger(chainURL, networkID)
	l.LogBalances(alice.WalletAddress(), bob.WalletAddress())

	// Open channel, transact, close.
	log.Println("Opening channel and depositing funds.")
	chAlice := alice.OpenChannel(bob.WireAddress(), 100000)
	chBob := bob.AcceptedChannel()

	log.Println("Sending payments...")
	chAlice.SendPayment(50000)
	chBob.SendPayment(25000)
	chAlice.SendPayment(25000)

	log.Println("Settling channel.")
	chAlice.Settle() // Conclude and withdraw.
	chBob.Settle()   // Withdraw.

	// Print balances after transactions.
	l.LogBalances(alice.WalletAddress(), bob.WalletAddress())

	// Cleanup.
	alice.Shutdown()
	bob.Shutdown()
}

Run from the command line

To run the example from the command line, start the local blockchain by calling the perun-polkadot-node. Make sure the port -p matches with the one of the chainURL in the environment constants.

docker run --rm -it -p 9944:9944 ghcr.io/perun-network/polkadot-test-node

In a second terminal, run the demo:

cd payment-channel-dot/
go run .

If everything works, you should see the following output.

2022/04/11 15:04:52 Setting up clients.
2022/04/11 15:04:52 Connecting to ws://127.0.0.1:9944...
2022/04/11 15:04:52 Connecting to ws://127.0.0.1:9944...
2022/04/11 15:04:52 Connecting to ws://127.0.0.1:9944...
2022/04/11 15:04:52 Client balances (DOT): [1.153 MDot 1.153 MDot]
2022/04/11 15:04:52 Opening channel and depositing funds.
2022/04/11 15:04:54 Sending payments...
2022/04/11 15:04:54 Settling channel.
2022/04/11 15:04:54 Adjudicator event: type = *channel.ConcludedEvent, client = 0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48
2022/04/11 15:04:59 Adjudicator event: type = *channel.ConcludedEvent, client = 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
2022/04/11 15:05:05 Client balances (DOT): [1.103 MDot 1.203 MDot]

With this, we conclude the migration tutorial.