chancloser: deny co-op close initiated by the chan initiator for frozen chans

This commit is contained in:
Olaoluwa Osuntokun 2020-03-13 16:58:05 -07:00
parent c85f6bb364
commit 1ac7550e3f
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465
2 changed files with 36 additions and 9 deletions

@ -347,6 +347,21 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
"instead have %v", spew.Sdump(msg)) "instead have %v", spew.Sdump(msg))
} }
// As we're the responder to this shutdown (the other party
// wants to close), we'll check if this is a frozen channel or
// not. If the channel is frozen as we were also the initiator
// of the channel opening, then we'll deny their close attempt.
chanInitiator := c.cfg.channel.IsInitiator()
if !chanInitiator && c.cfg.channel.State().ChanType.IsFrozen() &&
c.negotiationHeight < c.cfg.channel.State().ThawHeight {
return nil, false, fmt.Errorf("initiator attempting "+
"to co-op close frozen ChannelPoint(%v) "+
"(current_height=%v, thaw_height=%v)",
c.chanPoint, c.negotiationHeight,
c.cfg.channel.State().ThawHeight)
}
// If the remote node opened the channel with option upfront shutdown // If the remote node opened the channel with option upfront shutdown
// script, check that the script they provided matches. // script, check that the script they provided matches.
if err := maybeMatchScript( if err := maybeMatchScript(
@ -382,7 +397,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b
// We'll also craft our initial close proposal in order to keep // We'll also craft our initial close proposal in order to keep
// the negotiation moving, but only if we're the negotiator. // the negotiation moving, but only if we're the negotiator.
if c.cfg.channel.IsInitiator() { if chanInitiator {
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
if err != nil { if err != nil {
return nil, false, err return nil, false, err

@ -1470,8 +1470,8 @@ func extractOpenChannelMinConfs(in *lnrpc.OpenChannelRequest) (int32, error) {
// newFundingShimAssembler returns a new fully populated // newFundingShimAssembler returns a new fully populated
// chanfunding.CannedAssembler using a FundingShim obtained from an RPC caller. // chanfunding.CannedAssembler using a FundingShim obtained from an RPC caller.
func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool,
initiator bool, keyRing keychain.KeyRing) (chanfunding.Assembler, error) { keyRing keychain.KeyRing) (chanfunding.Assembler, error) {
// Perform some basic sanity checks to ensure that all the expected // Perform some basic sanity checks to ensure that all the expected
// fields are populated. // fields are populated.
@ -1545,8 +1545,9 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim,
// With all the parts assembled, we can now make the canned assembler // With all the parts assembled, we can now make the canned assembler
// to pass into the wallet. // to pass into the wallet.
return chanfunding.NewCannedAssembler( return chanfunding.NewCannedAssembler(
0, *chanPoint, btcutil.Amount(chanPointShim.Amt), chanPointShim.ThawHeight, *chanPoint,
&localKeyDesc, remoteKey, initiator, btcutil.Amount(chanPointShim.Amt), &localKeyDesc,
remoteKey, initiator,
), nil ), nil
} }
@ -1956,15 +1957,25 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
return err return err
} }
// If this is a frozen channel, then we only allow the close to proceed
// if we were the responder to this channel.
_, bestHeight, err := r.server.cc.chainIO.GetBestBlock()
if err != nil {
return err
}
if channel.State().ChanType.IsFrozen() && channel.IsInitiator() &&
uint32(bestHeight) < channel.State().ThawHeight {
return fmt.Errorf("cannot co-op close frozen channel as "+
"initiator until height=%v, (current_height=%v)",
channel.State().ThawHeight, bestHeight)
}
// If a force closure was requested, then we'll handle all the details // If a force closure was requested, then we'll handle all the details
// around the creation and broadcast of the unilateral closure // around the creation and broadcast of the unilateral closure
// transaction here rather than going to the switch as we don't require // transaction here rather than going to the switch as we don't require
// interaction from the peer. // interaction from the peer.
if force { if force {
_, bestHeight, err := r.server.cc.chainIO.GetBestBlock()
if err != nil {
return err
}
// As we're force closing this channel, as a precaution, we'll // As we're force closing this channel, as a precaution, we'll
// ensure that the switch doesn't continue to see this channel // ensure that the switch doesn't continue to see this channel
@ -3179,6 +3190,7 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve), RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve),
StaticRemoteKey: commitmentType == lnrpc.CommitmentType_STATIC_REMOTE_KEY, StaticRemoteKey: commitmentType == lnrpc.CommitmentType_STATIC_REMOTE_KEY,
CommitmentType: commitmentType, CommitmentType: commitmentType,
ThawHeight: dbChannel.ThawHeight,
} }
for i, htlc := range localCommit.Htlcs { for i, htlc := range localCommit.Htlcs {