contractcourt/channel_arbitrator: prevent force closing same channel twice

In this commit, we prevent the ChainArbitrator from sending a force
close request for a channel if it has previously already sent one. We do
this to prevent blocking the caller of ForceCloseContract.
This commit is contained in:
Wilmer Paulino 2018-10-31 10:38:27 -08:00
parent 7e69c6e7f6
commit 60036aca1d
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
2 changed files with 62 additions and 0 deletions

@ -1,6 +1,7 @@
package contractcourt
import (
"errors"
"sync"
"sync/atomic"
@ -21,6 +22,13 @@ const (
broadcastRedeemMultiplier = 2
)
var (
// errAlreadyForceClosed is an error returned when we attempt to force
// close a channel that's already in the process of doing so.
errAlreadyForceClosed = errors.New("channel is already in the " +
"process of being force closed")
)
// WitnessSubscription represents an intent to be notified once new witnesses
// are discovered by various active contract resolvers. A contract resolver may
// use this to be notified of when it can satisfy an incoming contract after we
@ -1652,6 +1660,16 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
// channel. We'll
case closeReq := <-c.forceCloseReqs:
if c.state != StateDefault {
select {
case closeReq.closeTx <- nil:
case <-c.quit:
}
select {
case closeReq.errResp <- errAlreadyForceClosed:
case <-c.quit:
}
continue
}

@ -1143,3 +1143,47 @@ func TestChannelArbitratorEmptyResolutions(t *testing.T) {
}
chanArb.Stop()
}
// TestChannelArbitratorAlreadyForceClosed ensures that we cannot force close a
// channel that is already in the process of doing so.
func TestChannelArbitratorAlreadyForceClosed(t *testing.T) {
t.Parallel()
// We'll create the arbitrator and its backing log to signal that it's
// already in the process of being force closed.
log := &mockArbitratorLog{
state: StateCommitmentBroadcasted,
}
chanArb, _, err := createTestChannelArbitrator(log)
if err != nil {
t.Fatalf("unable to create ChannelArbitrator: %v", err)
}
if err := chanArb.Start(); err != nil {
t.Fatalf("unable to start ChannelArbitrator: %v", err)
}
defer chanArb.Stop()
// Then, we'll create a request to signal a force close request to the
// channel arbitrator.
errChan := make(chan error, 1)
respChan := make(chan *wire.MsgTx, 1)
select {
case chanArb.forceCloseReqs <- &forceCloseReq{
closeTx: respChan,
errResp: errChan,
}:
case <-chanArb.quit:
}
// Finally, we should ensure that we are not able to do so by seeing the
// expected errAlreadyForceClosed error.
select {
case err = <-errChan:
if err != errAlreadyForceClosed {
t.Fatalf("expected errAlreadyForceClosed, got %v", err)
}
case <-time.After(time.Second):
t.Fatal("expected to receive error response")
}
}