diff --git a/chanbackup/single.go b/chanbackup/single.go index 3007559e0..6960f9016 100644 --- a/chanbackup/single.go +++ b/chanbackup/single.go @@ -40,6 +40,13 @@ const ( // AnchorsZeroFeeHtlcTxCommitVersion is a version that denotes this // channel is using the zero-fee second-level anchor commitment format. AnchorsZeroFeeHtlcTxCommitVersion = 3 + + // ScriptEnforcedLeaseVersion is a version that denotes this channel is + // using the zero-fee second-level anchor commitment format along with + // an additional CLTV requirement of the channel lease maturity on any + // commitment and HTLC outputs that pay directly to the channel + // initiator. + ScriptEnforcedLeaseVersion = 4 ) // Single is a static description of an existing channel that can be used for @@ -116,6 +123,16 @@ type Single struct { // ShaChainRootDesc describes how to derive the private key that was // used as the shachain root for this channel. ShaChainRootDesc keychain.KeyDescriptor + + // LeaseExpiry represents the absolute expiration as a height of the + // chain of a channel lease that is applied to every output that pays + // directly to the channel initiator in addition to the usual CSV + // requirement. + // + // NOTE: This field will only be present for the following versions: + // + // - ScriptEnforcedLeaseVersion + LeaseExpiry uint32 } // NewSingle creates a new static channel backup based on an existing open @@ -177,6 +194,10 @@ func NewSingle(channel *channeldb.OpenChannel, } switch { + case channel.ChanType.HasLeaseExpiration(): + single.Version = ScriptEnforcedLeaseVersion + single.LeaseExpiry = channel.ThawHeight + case channel.ChanType.ZeroHtlcTxFee(): single.Version = AnchorsZeroFeeHtlcTxCommitVersion @@ -203,6 +224,7 @@ func (s *Single) Serialize(w io.Writer) error { case TweaklessCommitVersion: case AnchorsCommitVersion: case AnchorsZeroFeeHtlcTxCommitVersion: + case ScriptEnforcedLeaseVersion: default: return fmt.Errorf("unable to serialize w/ unknown "+ "version: %v", s.Version) @@ -264,6 +286,12 @@ func (s *Single) Serialize(w io.Writer) error { ); err != nil { return err } + if s.Version == ScriptEnforcedLeaseVersion { + err := lnwire.WriteElements(&singleBytes, s.LeaseExpiry) + if err != nil { + return err + } + } // TODO(yy): remove the type assertion when we finished refactoring db // into using write buffer. @@ -370,6 +398,7 @@ func (s *Single) Deserialize(r io.Reader) error { case TweaklessCommitVersion: case AnchorsCommitVersion: case AnchorsZeroFeeHtlcTxCommitVersion: + case ScriptEnforcedLeaseVersion: default: return fmt.Errorf("unable to de-serialize w/ unknown "+ "version: %v", s.Version) @@ -463,8 +492,18 @@ func (s *Single) Deserialize(r io.Reader) error { return err } s.ShaChainRootDesc.KeyLocator.Family = keychain.KeyFamily(shaKeyFam) + err = lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index) + if err != nil { + return err + } - return lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index) + if s.Version == ScriptEnforcedLeaseVersion { + if err := lnwire.ReadElement(r, &s.LeaseExpiry); err != nil { + return err + } + } + + return nil } // UnpackFromReader is similar to Deserialize method, but it expects the passed diff --git a/chanbackup/single_test.go b/chanbackup/single_test.go index ee20d892f..97141ec9f 100644 --- a/chanbackup/single_test.go +++ b/chanbackup/single_test.go @@ -124,10 +124,7 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { isInitiator = true } - chanType := channeldb.SingleFunderBit - if rand.Int63()%2 == 0 { - chanType = channeldb.SingleFunderTweaklessBit - } + chanType := channeldb.ChannelType(rand.Intn(8)) return &channeldb.OpenChannel{ ChainHash: chainHash, @@ -137,6 +134,7 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) { ShortChannelID: lnwire.NewShortChanIDFromInt( uint64(rand.Int63()), ), + ThawHeight: rand.Uint32(), IdentityPub: pub, LocalChanCfg: channeldb.ChannelConfig{ ChannelConstraints: channeldb.ChannelConstraints{ @@ -243,6 +241,13 @@ func TestSinglePackUnpack(t *testing.T) { valid: true, }, + // The new script enforced channel lease version should + // pack/unpack with no problem. + { + version: ScriptEnforcedLeaseVersion, + valid: true, + }, + // A non-default version, atm this should result in a failure. { version: 99, @@ -293,8 +298,9 @@ func TestSinglePackUnpack(t *testing.T) { t.Fatalf("unable to serialize single: %v", err) } + // Mutate the version byte to an unknown version. rawBytes := rawSingle.Bytes() - rawBytes[0] ^= 5 + rawBytes[0] = ^uint8(0) newReader := bytes.NewReader(rawBytes) err = unpackedSingle.Deserialize(newReader) diff --git a/chanrestore.go b/chanrestore.go index cd68b5077..909c2e397 100644 --- a/chanrestore.go +++ b/chanrestore.go @@ -149,6 +149,12 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( chanType |= channeldb.AnchorOutputsBit chanType |= channeldb.SingleFunderTweaklessBit + case chanbackup.ScriptEnforcedLeaseVersion: + chanType = channeldb.LeaseExpirationBit + chanType |= channeldb.ZeroHtlcTxFeeBit + chanType |= channeldb.AnchorOutputsBit + chanType |= channeldb.SingleFunderTweaklessBit + default: return nil, fmt.Errorf("unknown Single version: %v", err) } @@ -172,6 +178,7 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( RemoteCurrentRevocation: backup.RemoteNodePub, RevocationStore: shachain.NewRevocationStore(), RevocationProducer: shaChainProducer, + ThawHeight: backup.LeaseExpiry, }, }