We now give the node an updateChannel function to update the channel by sending Ether from Bob to Alice.

func (n *node) updateChannel() error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	// Use UpdateBy to conveniently update the channels state.
	return n.ch.UpdateBy(ctx, func(state *channel.State) error {
		// Shift 5 ETH from bob to alice.
		amount := ethToWei(5)
		state.Balances[0][RoleBob].Sub(state.Balances[0][RoleBob], amount)
		state.Balances[0][RoleAlice].Add(state.Balances[0][RoleAlice], amount)
		// Finalize the channel, this will be important in the next step.
		state.IsFinal = true
		return nil

In the highlighted lines you can see that we use index 0 for the Balances slice. This means that we access the funds of the first asset. Since there is only one asset, this is the only entry. We also finalize the channel, which will be important later and implies that it can not be updated again.


go-perun checks that an update preserves the sum of each asset.


The update that was initiated with the updateChannel function above would then arrive at the HandleUpdate function of the other participant. In HandleUpdate you can decide on whether you want to accept the incoming update or not. This example function accepts all updates:

func (n *node) HandleUpdate(update client.ChannelUpdate, responder *client.UpdateResponder) {
	fmt.Printf("%v HandleUpdate Bals=%s\n", n.role, formatBalances(update.State.Balances))
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := responder.Accept(ctx); err != nil {
		fmt.Printf("Could not accept update: %v\n", err)

An update can also be rejected with a reason. This starts the dispute process.

responder.Reject(ctx, "do not like")