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.