Skip to content

Commit

Permalink
multi: add relative thaw height interpretation
Browse files Browse the repository at this point in the history
This is useful when we wish to have a channel frozen for a specific
amount of blocks after its confirmation. This could also be done with an
absolute thaw height, but it does not suit cases where a strict block
delta needs to be enforced, as it's not possible to know for certain
when a channel will be included in the chain. To work around this, we
add a relative interpretation of the field, where if its value is below
500,000, then it's interpreted as a relative height. This approach
allows us to prevent further database modifications to account for a
relative thaw height.
  • Loading branch information
wpaulino committed Jul 2, 2020
1 parent 01ba4d0 commit 6075997
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 38 deletions.
32 changes: 31 additions & 1 deletion channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import (
"github.com/lightningnetwork/lnd/shachain"
)

const (
// AbsoluteThawHeightThreshold is the threshold at which a thaw height
// begins to be interpreted as an absolute block height, rather than a
// relative one.
AbsoluteThawHeightThreshold uint32 = 500000
)

var (
// closedChannelBucket stores summarization information concerning
// previously open, but now closed channels.
Expand Down Expand Up @@ -654,7 +661,8 @@ type OpenChannel struct {

// ThawHeight is the height when a frozen channel once again becomes a
// normal channel. If this is zero, then there're no restrictions on
// this channel.
// this channel. If the value is lower than 500,000, then it's
// interpreted as a relative height, or an absolute height otherwise.
ThawHeight uint32

// TODO(roasbeef): eww
Expand Down Expand Up @@ -2766,6 +2774,28 @@ func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) {
return c.RevocationStore, nil
}

// AbsoluteThawHeight determines a frozen channel's absolute thaw height. If the
// channel is not frozen, then 0 is returned.
func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) {
// Only frozen channels have a thaw height.
if !c.ChanType.IsFrozen() {
return 0, nil
}

// If the channel's thaw height is below the absolute threshold, then
// it's interpreted as a relative height to the chain's current height.
if c.ThawHeight < AbsoluteThawHeightThreshold {
// We'll only known of the channel's short ID once it's
// confirmed.
if c.IsPending {
return 0, errors.New("cannot use relative thaw " +
"height for unconfirmed channel")
}
return c.ShortChannelID.BlockHeight + c.ThawHeight, nil
}
return c.ThawHeight, nil
}

func putChannelCloseSummary(tx kvdb.RwTx, chanID []byte,
summary *ChannelCloseSummary, lastChanState *OpenChannel) error {

Expand Down
13 changes: 8 additions & 5 deletions lnrpc/rpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,9 @@ message Channel {
frozen channel doest not allow a cooperative channel close by the
initiator. The thaw_height is the height that this restriction stops
applying to the channel. This field is optional, not setting it or using a
value of zero will mean the channel has no additional restrictions.
value of zero will mean the channel has no additional restrictions. The
height can be interpreted in two ways: as a relative height if the value is
less than 500,000, or as an absolute height otherwise.
*/
uint32 thaw_height = 28;

Expand Down Expand Up @@ -1665,10 +1667,11 @@ message ChanPointShim {
bytes pending_chan_id = 5;

/*
This uint32 indicates if this channel is to be considered 'frozen'. A
frozen channel does not allow a cooperative channel close by the
initiator. The thaw_height is the height that this restriction stops
applying to the channel.
This uint32 indicates if this channel is to be considered 'frozen'. A frozen
channel does not allow a cooperative channel close by the initiator. The
thaw_height is the height that this restriction stops applying to the
channel. The height can be interpreted in two ways: as a relative height if
the value is less than 500,000, or as an absolute height otherwise.
*/
uint32 thaw_height = 6;
}
Expand Down
4 changes: 2 additions & 2 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2466,7 +2466,7 @@
"thaw_height": {
"type": "integer",
"format": "int64",
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A\nfrozen channel does not allow a cooperative channel close by the\ninitiator. The thaw_height is the height that this restriction stops\napplying to the channel."
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A frozen\nchannel does not allow a cooperative channel close by the initiator. The\nthaw_height is the height that this restriction stops applying to the\nchannel. The height can be interpreted in two ways: as a relative height if\nthe value is less than 500,000, or as an absolute height otherwise."
}
}
},
Expand Down Expand Up @@ -2608,7 +2608,7 @@
"thaw_height": {
"type": "integer",
"format": "int64",
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A\nfrozen channel doest not allow a cooperative channel close by the\ninitiator. The thaw_height is the height that this restriction stops\napplying to the channel. This field is optional, not setting it or using a\nvalue of zero will mean the channel has no additional restrictions."
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A\nfrozen channel doest not allow a cooperative channel close by the\ninitiator. The thaw_height is the height that this restriction stops\napplying to the channel. This field is optional, not setting it or using a\nvalue of zero will mean the channel has no additional restrictions. The\nheight can be interpreted in two ways: as a relative height if the value is\nless than 500,000, or as an absolute height otherwise."
},
"local_constraints": {
"$ref": "#/definitions/lnrpcChannelConstraints",
Expand Down
7 changes: 1 addition & 6 deletions lntest/itest/lnd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14501,11 +14501,6 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to gen pending chan ID: %v", err)
}

_, currentHeight, err := net.Miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("unable to get current blockheight %v", err)
}

// Now that we have the pending channel ID, Dave (our responder) will
// register the intent to receive a new channel funding workflow using
// the pending channel ID.
Expand All @@ -14514,7 +14509,7 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
FundingTxidBytes: txid[:],
},
}
thawHeight := uint32(currentHeight + 10)
thawHeight := uint32(10)
chanPointShim := &lnrpc.ChanPointShim{
Amt: int64(chanSize),
ChanPoint: chanPoint,
Expand Down
28 changes: 17 additions & 11 deletions lnwallet/chancloser/chancloser.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,19 +355,25 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
"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 and we were not also the initiator of the channel
// opening, then we'll deny their close attempt.
// 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 and we were not also the
// initiator of the channel opening, then we'll deny their close
// attempt.
chanInitiator := c.cfg.Channel.IsInitiator()
chanState := c.cfg.Channel.State()
if !chanInitiator && chanState.ChanType.IsFrozen() &&
c.negotiationHeight < chanState.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,
chanState.ThawHeight)
if !chanInitiator {
absoluteThawHeight, err := chanState.AbsoluteThawHeight()
if err != nil {
return nil, false, err
}
if c.negotiationHeight < absoluteThawHeight {
return nil, false, fmt.Errorf("initiator "+
"attempting to co-op close frozen "+
"ChannelPoint(%v) (current_height=%v, "+
"thaw_height=%v)", c.chanPoint,
c.negotiationHeight, absoluteThawHeight)
}
}

// If the remote node opened the channel with option upfront shutdown
Expand Down
21 changes: 13 additions & 8 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2122,14 +2122,19 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
})
} else {
// If this is a frozen channel, then we only allow the co-op
// close to proceed if we were the responder to this channel.
if channel.ChanType.IsFrozen() && channel.IsInitiator &&
uint32(bestHeight) < channel.ThawHeight {

return fmt.Errorf("cannot co-op close frozen channel "+
"as initiator until height=%v, "+
"(current_height=%v)", channel.ThawHeight,
bestHeight)
// close to proceed if we were the responder to this channel if
// the absolute thaw height has not been met.
if channel.IsInitiator {
absoluteThawHeight, err := channel.AbsoluteThawHeight()
if err != nil {
return err
}
if uint32(bestHeight) < absoluteThawHeight {
return fmt.Errorf("cannot co-op close frozen "+
"channel as initiator until height=%v, "+
"(current_height=%v)",
absoluteThawHeight, bestHeight)
}
}

// If the link is not known by the switch, we cannot gracefully close
Expand Down

0 comments on commit 6075997

Please sign in to comment.