Test๏ƒ

We now create a simple test for our app channel client by setting up two clients, opening an app channel between them, and playing one round of Tic-Tac-Toe. Everything in this section will take place in package main.

package main

Utilities๏ƒ

We construct helpers in util.go for contract deployment, client generation, and logging of balances.

Note

These are very similar to the payment channel setup; hence only the additions in deployContracts and setupGameClient are mentioned here.

Deploy contracts. Besides the Adjudicator and Asset Holder, the TicTacToeApp.sol app contract is required to be deployed. Its go binding provides ticTacToeApp.DeployTicTacToeApp to realize this.

Setup game client. The app and stake arguments are added here to satisfy the clientโ€™s constructor client.SetupAppClient.

Main routine๏ƒ

An exemplary round of Tic-Tac-Toe is played. We put the code of this section into main.go. Ultimately, you can run main.go to see the individual steps executing in your command line output.

Configuration. We are using the same configuration as for the payment channel example: chainURL and chainID identify the blockchain we want to run on. In our case, an instance of the local chain is provided by the ganache-cli. Three private keys are required. Some party deploying the contracts, and Alice & Bob who want to use the app channel.

const (
	chainURL = "ws://127.0.0.1:8545"
	chainID  = 1337

	// Private keys.
	keyDeployer = "79ea8f62d97bc0591a4224c1725fca6b00de5b2cea286fe2e0bb35c5e76be46e"
	keyAlice    = "1af2e950272dd403de7a5760d41c6e44d92b6d02797e51810795ff03cc2cda4f"
	keyBob      = "f63d7d8e930bccd74e93cf5662fde2c28fd8be95edb70c73f1bdd863d07f412e"
)

Contract deployment. We call deployContracts with the corresponding arguments to receive the adjudicator, assetHolder, and appAddress. Using appAddress we initialize a new TicTacToeApp.

// main runs a demo of the game 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() {
	// Deploy contracts.
	log.Println("Deploying contracts.")
	adjudicator, assetHolder, appAddress := deployContracts(chainURL, chainID, keyDeployer)
	asset := *ethwallet.AsWalletAddr(assetHolder)
	app := app.NewTicTacToeApp(ethwallet.AsWalletAddr(appAddress))

Client setup. We create a new message bus via wire.NewLocalBus, which will be used by the clients to communicate with each other. Then we call the setupGameClient for both Alice and Bob. The balance logger is initialized via newBalanceLogger and LogBalances prints the initial balance of both clients.

	// Setup clients.
	log.Println("Setting up clients.")
	bus := wire.NewLocalBus() // Message bus used for off-chain communication.
	stake := client.EthToWei(big.NewFloat(5))
	alice := setupGameClient(bus, chainURL, adjudicator, asset, keyAlice, app, stake)
	bob := setupGameClient(bus, chainURL, adjudicator, asset, keyBob, app, stake)

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

Open app channel and play. Alice opens a channel with OpenAppChannel with Bob, where she specifies the amount she wants to put into the channel. Bob fetches the new channel from his registry by calling AcceptedChannel. Now everything is set up, and we let Alice and Bob play by calling Set alternately.

	// Open app channel and play.
	log.Println("Opening channel.")
	appAlice := alice.OpenAppChannel(bob.WireAddress())
	appBob := bob.AcceptedChannel()

	log.Println("Start playing.")
	log.Println("Alice's turn.")
	appAlice.Set(2, 0)

	log.Println("Bob's turn.")
	appBob.Set(0, 0)

	log.Println("Alice's turn.")
	appAlice.Set(0, 2)

	log.Println("Bob's turn.")
	appBob.Set(1, 1)

	log.Println("Alice's turn.")
	appAlice.Set(2, 2)

	log.Println("Bob's turn.")
	appBob.Set(2, 1)

Dispute. Suppose Alice proposed her winning move with Set(1, 2) but Bob is malicious and did not accept this new state in an attempt to save his funds. Alice uses ForceSet to enforce her winning game move then. Bob cannot do anything to prohibit Alice from winning at this point because the adjudicator (with the corresponding on-chain validTransition logic) decides over Aliceโ€™s proposed update.

	// Dispute channel state.
	log.Println("Alice's turn.")
	appAlice.ForceSet(1, 2)

	log.Println("Alice wins.")
	log.Println("Payout.")

Settle. Alice wins in our case, and we settle to conclude and withdraw the funds from the channel. Finally, both clients shut down to free up the used resources.

	// Payout.
	appAlice.Settle()
	appBob.Settle()

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

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

Run from command line๏ƒ

We now execute our test case from the command line.

First, we start a local Ethereum blockchain. We run ganache-cli exactly like we did in the payment channel example. Please make sure that the constants match the ones used in the client configuration.

KEY_DEPLOYER=0x79ea8f62d97bc0591a4224c1725fca6b00de5b2cea286fe2e0bb35c5e76be46e
KEY_ALICE=0x1af2e950272dd403de7a5760d41c6e44d92b6d02797e51810795ff03cc2cda4f
KEY_BOB=0xf63d7d8e930bccd74e93cf5662fde2c28fd8be95edb70c73f1bdd863d07f412e
BALANCE=10000000000000000000

ganache-cli --host 127.0.0.1 --port 8545 --account $KEY_DEPLOYER,$BALANCE --account $KEY_ALICE,$BALANCE --account $KEY_BOB,$BALANCE --blockTime=5

Now run the tutorial application with:

go run .

You should be able to observe the following output:

Note

Because we included a dispute, there is more chain interaction than in the optimistic case; therefore, we have multiple adjudicator events. The RegisteredEvent signals that the forced state got successfully registered on-chain. Following that, the ProgressedEvent is triggered, which signals on-chain progression. Finally, the ConcludedEvent occurs during settlement.

2022/03/24 09:44:40 Deploying contracts.
2022/03/24 09:44:53 Setting up clients.
2022/03/24 09:44:54 Client balances (ETH): [10 10]
2022/03/24 09:44:54 Opening channel.
2022/03/24 09:44:59 Start playing.
2022/03/24 09:44:59 Alice's turn.
2022/03/24 09:44:59
 | |x
 | |
 | |
Next actor: 1

2022/03/24 09:44:59 Bob's turn.
2022/03/24 09:44:59
o| |x
 | |
 | |
Next actor: 0

2022/03/24 09:44:59 Alice's turn.
2022/03/24 09:44:59
o| |x
 | |
x| |
Next actor: 1

2022/03/24 09:44:59 Bob's turn.
2022/03/24 09:44:59
o| |x
 |o|
x| |
Next actor: 0

2022/03/24 09:44:59 Alice's turn.
2022/03/24 09:44:59
o| |x
 |o|
x| |x
Next actor: 1

2022/03/24 09:44:59 Bob's turn.
2022/03/24 09:44:59
o| |x
 |o|o
x| |x
Next actor: 0

2022/03/24 09:44:59 Alice's turn.
2022/03/24 09:45:03 Adjudicator event: type = *channel.RegisteredEvent, client = 0x6536425BE95A6661F6C6f68D709B6BE152785Df6
2022/03/24 09:45:13 Adjudicator event: type = *channel.RegisteredEvent, client = 0x56FD289cEe714a5E471c418436EFA63E780D7a87
2022/03/24 09:45:13
o| |x
 |o|o
x|x|x
Next actor: 1

2022/03/24 09:45:18 Adjudicator event: type = *channel.ProgressedEvent, client = 0x6536425BE95A6661F6C6f68D709B6BE152785Df6
2022/03/24 09:45:19 Alice wins.
2022/03/24 09:45:19 Payout.
2022/03/24 09:45:19 Adjudicator event: type = *channel.ProgressedEvent, client = 0x56FD289cEe714a5E471c418436EFA63E780D7a87
2022/03/24 09:45:24 Adjudicator event: type = *channel.ConcludedEvent, client = 0x6536425BE95A6661F6C6f68D709B6BE152785Df6
2022/03/24 09:45:30 Adjudicator event: type = *channel.ConcludedEvent, client = 0x56FD289cEe714a5E471c418436EFA63E780D7a87
2022/03/24 09:45:30 Client balances (ETH): [14.999242656 4.99990828]

With this, we conclude our app channel tutorial.