diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 422e6be7e29..57612626c57 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -2296,6 +2296,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, + false, 0, ) if err != nil { return nil, nil, nil, err diff --git a/chanacceptor/rpcacceptor.go b/chanacceptor/rpcacceptor.go index 66d846b3038..bec91435599 100644 --- a/chanacceptor/rpcacceptor.go +++ b/chanacceptor/rpcacceptor.go @@ -256,6 +256,42 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error, req := newRequest.request pendingChanID := req.OpenChanMsg.PendingChannelID + // Map the channel commitment type to its RPC + // counterpart. + var commitmentType lnrpc.CommitmentType + if req.OpenChanMsg.ChannelType != nil { + channelFeatures := lnwire.RawFeatureVector( + *req.OpenChanMsg.ChannelType, + ) + switch { + case channelFeatures.OnlyContains( + lnwire.ScriptEnforcedLeaseRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.StaticRemoteKeyRequired, + ): + commitmentType = lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE + + case channelFeatures.OnlyContains( + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.StaticRemoteKeyRequired, + ): + commitmentType = lnrpc.CommitmentType_ANCHORS + + case channelFeatures.OnlyContains( + lnwire.StaticRemoteKeyRequired, + ): + commitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY + + case channelFeatures.OnlyContains(): + commitmentType = lnrpc.CommitmentType_LEGACY + + default: + log.Warnf("Unhandled commitment type "+ + "in channel acceptor request: %v", + req.OpenChanMsg.ChannelType) + } + } + acceptRequests[pendingChanID] = newRequest // A ChannelAcceptRequest has been received, send it to the client. @@ -273,6 +309,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error, CsvDelay: uint32(req.OpenChanMsg.CsvDelay), MaxAcceptedHtlcs: uint32(req.OpenChanMsg.MaxAcceptedHTLCs), ChannelFlags: uint32(req.OpenChanMsg.ChannelFlags), + CommitmentType: commitmentType, } if err := r.send(chanAcceptReq); err != nil { diff --git a/chanbackup/single.go b/chanbackup/single.go index e51700c886d..d8a12525750 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 + } + } return lnwire.WriteElements( w, @@ -363,6 +391,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) @@ -456,8 +485,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 + } + + if s.Version == ScriptEnforcedLeaseVersion { + if err := lnwire.ReadElement(r, &s.LeaseExpiry); err != nil { + return err + } + } - return lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index) + 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 ee20d892f6b..97141ec9f3d 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/channeldb/channel.go b/channeldb/channel.go index 7414c59273d..f85dafd5b00 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -257,6 +257,11 @@ const ( // ZeroHtlcTxFeeBit indicates that the channel should use zero-fee // second-level HTLC transactions. ZeroHtlcTxFeeBit ChannelType = 1 << 5 + + // LeaseExpirationBit indicates that the channel has been leased for a + // period of time, constraining every output that pays to the channel + // initiator with an additional CLTV of the lease maturity. + LeaseExpirationBit ChannelType = 1 << 6 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -301,6 +306,11 @@ func (c ChannelType) IsFrozen() bool { return c&FrozenBit == FrozenBit } +// HasLeaseExpiration returns true if the channel originated from a lease. +func (c ChannelType) HasLeaseExpiration() bool { + return c&LeaseExpirationBit == LeaseExpirationBit +} + // ChannelConstraints represents a set of constraints meant to allow a node to // limit their exposure, enact flow control and ensure that all HTLCs are // economically relevant. This struct will be mirrored for both sides of the @@ -1363,7 +1373,7 @@ func putOpenChannel(chanBucket kvdb.RwBucket, channel *OpenChannel) error { // Next, if this is a frozen channel, we'll add in the axillary // information we need to store. - if channel.ChanType.IsFrozen() { + if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() { err := storeThawHeight( chanBucket, channel.ThawHeight, ) @@ -1404,7 +1414,7 @@ func fetchOpenChannel(chanBucket kvdb.RBucket, // Next, if this is a frozen channel, we'll add in the axillary // information we need to store. - if channel.ChanType.IsFrozen() { + if channel.ChanType.IsFrozen() || channel.ChanType.HasLeaseExpiration() { thawHeight, err := fetchThawHeight(chanBucket) if err != nil { return nil, fmt.Errorf("unable to store thaw "+ @@ -2871,7 +2881,7 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary, // We'll also remove the channel from the frozen channel bucket // if we need to. - if c.ChanType.IsFrozen() { + if c.ChanType.IsFrozen() || c.ChanType.HasLeaseExpiration() { err := deleteThawHeight(chanBucket) if err != nil { return err @@ -3071,13 +3081,14 @@ func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) { // 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() { + if !c.ChanType.IsFrozen() && !c.ChanType.HasLeaseExpiration() { 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 { + // If the channel has the frozen bit set and it's thaw height is below + // the absolute threshold, then it's interpreted as a relative height to + // the chain's current height. + if c.ChanType.IsFrozen() && c.ThawHeight < AbsoluteThawHeightThreshold { // We'll only known of the channel's short ID once it's // confirmed. if c.IsPending { @@ -3086,6 +3097,7 @@ func (c *OpenChannel) AbsoluteThawHeight() (uint32, error) { } return c.ShortChannelID.BlockHeight + c.ThawHeight, nil } + return c.ThawHeight, nil } diff --git a/chanrestore.go b/chanrestore.go index 7527499cd3b..6013568875a 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, }, } diff --git a/cmd/lncli/cmd_open_channel.go b/cmd/lncli/cmd_open_channel.go index a2c1a530359..cac46cae35b 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/lncli/cmd_open_channel.go @@ -57,6 +57,9 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): ` // the user from choosing a large file by accident and running into out // of memory issues or other weird errors. psbtMaxFileSize = 1024 * 1024 + + channelTypeTweakless = "tweakless" + channelTypeAnchors = "anchors" ) // TODO(roasbeef): change default number of confirmations @@ -200,6 +203,12 @@ var openChannelCommand = cli.Command{ Usage: "(optional) the maximum value in msat that " + "can be pending within the channel at any given time", }, + cli.StringFlag{ + Name: "channel_type", + Usage: fmt.Sprintf("(optional) the type of channel to "+ + "propose to the remote peer (%q, %q)", + channelTypeTweakless, channelTypeAnchors), + }, }, Action: actionDecorator(openChannel), } @@ -307,6 +316,19 @@ func openChannel(ctx *cli.Context) error { req.Private = ctx.Bool("private") + // Parse the channel type and map it to its RPC representation. + channelType := ctx.String("channel_type") + switch channelType { + case "": + break + case channelTypeTweakless: + req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY + case channelTypeAnchors: + req.CommitmentType = lnrpc.CommitmentType_ANCHORS + default: + return fmt.Errorf("unsupported channel type %v", channelType) + } + // PSBT funding is a more involved, interactive process that is too // large to also fit into this already long function. if ctx.Bool("psbt") { diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go index 98a4fccae6a..fe2adc8b144 100644 --- a/contractcourt/anchor_resolver.go +++ b/contractcourt/anchor_resolver.go @@ -192,6 +192,13 @@ func (c *anchorResolver) IsResolved() bool { return c.resolved } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (c *anchorResolver) SupplementState(_ *channeldb.OpenChannel) { +} + // report returns a report on the resolution state of the contract. func (c *anchorResolver) report() *ContractReport { c.reportLock.Lock() diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index 453f292b662..55e2d2ea1b3 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -350,6 +350,9 @@ func newActiveChannelArbitrator(channel *channeldb.OpenChannel, report, ) }, + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { + return c.chanSource.FetchHistoricalChannel(&chanPoint) + }, } // The final component needed is an arbitrator log that the arbitrator @@ -558,6 +561,9 @@ func (c *ChainArbitrator) Start() error { tx, c.cfg.ChainHash, &chanPoint, report, ) }, + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { + return c.chanSource.FetchHistoricalChannel(&chanPoint) + }, } chanLog, err := newBoltArbitratorLog( c.chanSource.Backend, arbCfg, c.cfg.ChainHash, chanPoint, diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index ebe7e5fd98a..973a0ca73ff 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -362,8 +362,13 @@ func (c *chainWatcher) handleUnknownLocalState( // With the keys derived, we'll construct the remote script that'll be // present if they have a non-dust balance on the commitment. + var leaseExpiry uint32 + if c.cfg.chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = c.cfg.chanState.ThawHeight + } remoteScript, _, err := lnwallet.CommitScriptToRemote( - c.cfg.chanState.ChanType, commitKeyRing.ToRemoteKey, + c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, + commitKeyRing.ToRemoteKey, leaseExpiry, ) if err != nil { return false, err @@ -372,19 +377,15 @@ func (c *chainWatcher) handleUnknownLocalState( // Next, we'll derive our script that includes the revocation base for // the remote party allowing them to claim this output before the CSV // delay if we breach. - localScript, err := input.CommitScriptToSelf( - uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), + localScript, err := lnwallet.CommitScriptToSelf( + c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey, + uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry, ) if err != nil { return false, err } - localPkScript, err := input.WitnessScriptHash(localScript) - if err != nil { - return false, err - } - // With all our scripts assembled, we'll examine the outputs of the // commitment transaction to determine if this is a local force close // or not. @@ -393,7 +394,7 @@ func (c *chainWatcher) handleUnknownLocalState( pkScript := output.PkScript switch { - case bytes.Equal(localPkScript, pkScript): + case bytes.Equal(localScript.PkScript, pkScript): ourCommit = true case bytes.Equal(remoteScript.PkScript, pkScript): diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 5f4cb31d567..34d806dc5c3 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -158,6 +158,11 @@ type ChannelArbitratorConfig struct { PutResolverReport func(tx kvdb.RwTx, report *channeldb.ResolverReport) error + // FetchHistoricalChannel retrieves the historical state of a channel. + // This is mostly used to supplement the ContractResolvers with + // additional information required for proper contract resolution. + FetchHistoricalChannel func() (*channeldb.OpenChannel, error) + ChainArbitratorConfig } @@ -604,10 +609,20 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, htlcMap[outpoint] = &htlc } + // We'll also fetch the historical state of this channel, as it should + // have been marked as closed by now, and supplement it to each resolver + // such that we can properly resolve our pending contracts. + chanState, err := c.cfg.FetchHistoricalChannel() + if err != nil { + return err + } + log.Infof("ChannelArbitrator(%v): relaunching %v contract "+ "resolvers", c.cfg.ChanPoint, len(unresolvedContracts)) for _, resolver := range unresolvedContracts { + resolver.SupplementState(chanState) + htlcResolver, ok := resolver.(htlcContractResolver) if !ok { continue @@ -1907,6 +1922,14 @@ func (c *ChannelArbitrator) prepContractResolutions( return nil, nil, err } + // We'll also fetch the historical state of this channel, as it should + // have been marked as closed by now, and supplement it to each resolver + // such that we can properly resolve our pending contracts. + chanState, err := c.cfg.FetchHistoricalChannel() + if err != nil { + return nil, nil, err + } + // There may be a class of HTLC's which we can fail back immediately, // for those we'll prepare a slice of packets to add to our outbox. Any // packets we need to send, will be cancels. @@ -2012,6 +2035,7 @@ func (c *ChannelArbitrator) prepContractResolutions( resolver := newTimeoutResolver( resolution, height, htlc, resolverCfg, ) + resolver.SupplementState(chanState) htlcResolvers = append(htlcResolvers, resolver) } @@ -2068,6 +2092,7 @@ func (c *ChannelArbitrator) prepContractResolutions( resolver := newOutgoingContestResolver( resolution, height, htlc, resolverCfg, ) + resolver.SupplementState(chanState) htlcResolvers = append(htlcResolvers, resolver) } } @@ -2078,9 +2103,10 @@ func (c *ChannelArbitrator) prepContractResolutions( // trimmed). if contractResolutions.CommitResolution != nil { resolver := newCommitSweepResolver( - *contractResolutions.CommitResolution, - height, c.cfg.ChanPoint, resolverCfg, + *contractResolutions.CommitResolution, height, + c.cfg.ChanPoint, resolverCfg, ) + resolver.SupplementState(chanState) htlcResolvers = append(htlcResolvers, resolver) } diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 34eab1e31bf..c2bf055fb53 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -381,6 +381,9 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog, return nil }, + FetchHistoricalChannel: func() (*channeldb.OpenChannel, error) { + return &channeldb.OpenChannel{}, nil + }, } // Apply all custom options to the config struct. diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index 1ce3f3229f1..6b365735f58 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" "io" + "math" "sync" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -43,6 +44,19 @@ type commitSweepResolver struct { // chanPoint is the channel point of the original contract. chanPoint wire.OutPoint + // channelInitiator denotes whether the party responsible for resolving + // the contract initiated the channel. + channelInitiator bool + + // leaseExpiry denotes the additional waiting period the contract must + // hold until it can be resolved. This waiting period is known as the + // expiration of a script-enforced leased channel and only applies to + // the channel initiator. + // + // NOTE: This value should only be set when the contract belongs to a + // leased channel. + leaseExpiry uint32 + // currentReport stores the current state of the resolver for reporting // over the rpc interface. currentReport ContractReport @@ -55,8 +69,8 @@ type commitSweepResolver struct { // newCommitSweepResolver instantiates a new direct commit output resolver. func newCommitSweepResolver(res lnwallet.CommitOutputResolution, - broadcastHeight uint32, - chanPoint wire.OutPoint, resCfg ResolverConfig) *commitSweepResolver { + broadcastHeight uint32, chanPoint wire.OutPoint, + resCfg ResolverConfig) *commitSweepResolver { r := &commitSweepResolver{ contractResolverKit: *newContractResolverKit(resCfg), @@ -181,7 +195,14 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { return nil, err } + // Wait up until the CSV expires, unless we also have a CLTV that + // expires after. unlockHeight := confHeight + c.commitResolution.MaturityDelay + if c.hasCLTV() { + unlockHeight = uint32(math.Max( + float64(unlockHeight), float64(c.leaseExpiry), + )) + } c.log.Debugf("commit conf_height=%v, unlock_height=%v", confHeight, unlockHeight) @@ -191,14 +212,34 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { c.currentReport.MaturityHeight = unlockHeight c.reportLock.Unlock() - // If there is a csv delay, we'll wait for that. - if c.commitResolution.MaturityDelay > 0 { - c.log.Debugf("waiting for csv lock to expire at height %v", - unlockHeight) + // If there is a csv/cltv lock, we'll wait for that. + if c.commitResolution.MaturityDelay > 0 || c.hasCLTV() { + // Determine what height we should wait until for the locks to + // expire. + var waitHeight uint32 + switch { + // If we have both a csv and cltv lock, we'll need to look at + // both and see which expires later. + case c.commitResolution.MaturityDelay > 0 && c.hasCLTV(): + c.log.Debugf("waiting for CSV and CLTV lock to expire "+ + "at height %v", unlockHeight) + // If the CSV expires after the CLTV, or there is no + // CLTV, then we can broadcast a sweep a block before. + // Otherwise, we need to broadcast at our expected + // unlock height. + waitHeight = uint32(math.Max( + float64(unlockHeight-1), float64(c.leaseExpiry), + )) + + // If we only have a csv lock, wait for the height before the + // lock expires as the spend path should be unlocked by then. + case c.commitResolution.MaturityDelay > 0: + c.log.Debugf("waiting for CSV lock to expire at "+ + "height %v", unlockHeight) + waitHeight = unlockHeight - 1 + } - // We only need to wait for the block before the block that - // unlocks the spend path. - err := waitForHeight(unlockHeight-1, c.Notifier, c.quit) + err := waitForHeight(waitHeight, c.Notifier, c.quit) if err != nil { return nil, err } @@ -222,10 +263,20 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { var witnessType input.WitnessType switch { + // Delayed output to us on our local commitment for a channel lease in + // which we are the initiator. + case isLocalCommitTx && c.hasCLTV(): + witnessType = input.LeaseCommitmentTimeLock + // Delayed output to us on our local commitment. case isLocalCommitTx: witnessType = input.CommitmentTimeLock + // A confirmed output to us on the remote commitment for a channel lease + // in which we are the initiator. + case isDelayedOutput && c.hasCLTV(): + witnessType = input.LeaseCommitmentToRemoteConfirmed + // A confirmed output to us on the remote commitment. case isDelayedOutput: witnessType = input.CommitmentToRemoteConfirmed @@ -246,13 +297,21 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // We'll craft an input with all the information required for // the sweeper to create a fully valid sweeping transaction to // recover these coins. - inp := input.NewCsvInput( - &c.commitResolution.SelfOutPoint, - witnessType, - &c.commitResolution.SelfOutputSignDesc, - c.broadcastHeight, - c.commitResolution.MaturityDelay, - ) + var inp *input.BaseInput + if c.hasCLTV() { + inp = input.NewCsvInputWithCltv( + &c.commitResolution.SelfOutPoint, witnessType, + &c.commitResolution.SelfOutputSignDesc, + c.broadcastHeight, c.commitResolution.MaturityDelay, + c.leaseExpiry, + ) + } else { + inp = input.NewCsvInput( + &c.commitResolution.SelfOutPoint, witnessType, + &c.commitResolution.SelfOutputSignDesc, + c.broadcastHeight, c.commitResolution.MaturityDelay, + ) + } // With our input constructed, we'll now offer it to the // sweeper. @@ -337,6 +396,23 @@ func (c *commitSweepResolver) IsResolved() bool { return c.resolved } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) { + if state.ChanType.HasLeaseExpiration() { + c.leaseExpiry = state.ThawHeight + } + c.channelInitiator = state.IsInitiator +} + +// hasCLTV denotes whether the resolver must wait for an additional CLTV to +// expire before resolving the contract. +func (c *commitSweepResolver) hasCLTV() bool { + return c.channelInitiator && c.leaseExpiry > 0 +} + // Encode writes an encoded version of the ContractResolver into the passed // Writer. // diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index ef391ff939e..b12c4815c47 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -48,6 +48,10 @@ type ContractResolver interface { // NOTE: This function MUST be run as a goroutine. Resolve() (ContractResolver, error) + // SupplementState allows the user of a ContractResolver to supplement + // it with state required for the proper resolution of a contract. + SupplementState(*channeldb.OpenChannel) + // IsResolved returns true if the stored state in the resolve is fully // resolved. In this case the target output can be forgotten. IsResolved() bool diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 59f5d9fdb56..ca4e05ddf53 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -433,6 +433,13 @@ func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) { h.htlc = htlc } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (h *htlcIncomingContestResolver) SupplementState(_ *channeldb.OpenChannel) { +} + // decodePayload (re)decodes the hop payload of a received htlc. func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload, error) { diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index d2813c955bd..14398e1a761 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -190,6 +190,14 @@ func (h *htlcOutgoingContestResolver) IsResolved() bool { return h.resolved } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (h *htlcOutgoingContestResolver) SupplementState(state *channeldb.OpenChannel) { + h.htlcTimeoutResolver.SupplementState(state) +} + // Encode writes an encoded version of the ContractResolver into the passed // Writer. // diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 37b4d42b66c..c81fffcc8b3 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -626,6 +626,13 @@ func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) { h.htlc = htlc } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (h *htlcSuccessResolver) SupplementState(_ *channeldb.OpenChannel) { +} + // HtlcPoint returns the htlc's outpoint on the commitment tx. // // NOTE: Part of the htlcContractResolver interface. diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 44be1643340..b20f10ca9e7 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" "io" + "math" "sync" "github.com/btcsuite/btcd/wire" @@ -47,6 +48,19 @@ type htlcTimeoutResolver struct { // htlc contains information on the htlc that we are resolving on-chain. htlc channeldb.HTLC + // channelInitiator denotes whether the party responsible for resolving + // the contract initiated the channel. + channelInitiator bool + + // leaseExpiry denotes the additional waiting period the contract must + // hold until it can be resolved. This waiting period is known as the + // expiration of a script-enforced leased channel and only applies to + // the channel initiator. + // + // NOTE: This value should only be set when the contract belongs to a + // leased channel. + leaseExpiry uint32 + // currentReport stores the current state of the resolver for reporting // over the rpc interface. This should only be reported in case we have // a non-nil SignDetails on the htlcResolution, otherwise the nursery @@ -429,19 +443,30 @@ func (h *htlcTimeoutResolver) handleCommitSpend( switch { // If the sweeper is handling the second level transaction, wait for - // the CSV lock to expire, before sweeping the output on the - // second-level. + // the CSV and possible CLTV lock to expire, before sweeping the output + // on the second-level. case h.htlcResolution.SignDetails != nil: waitHeight := uint32(commitSpend.SpendingHeight) + h.htlcResolution.CsvDelay - 1 + if h.hasCLTV() { + waitHeight = uint32(math.Max( + float64(waitHeight), float64(h.leaseExpiry), + )) + } h.reportLock.Lock() h.currentReport.Stage = 2 h.currentReport.MaturityHeight = waitHeight h.reportLock.Unlock() - log.Infof("%T(%x): waiting for CSV lock to expire at height %v", - h, h.htlc.RHash[:], waitHeight) + if h.hasCLTV() { + log.Infof("%T(%x): waiting for CSV and CLTV lock to "+ + "expire at height %v", h, h.htlc.RHash[:], + waitHeight) + } else { + log.Infof("%T(%x): waiting for CSV lock to expire at "+ + "height %v", h, h.htlc.RHash[:], waitHeight) + } err := waitForHeight(waitHeight, h.Notifier, h.quit) if err != nil { @@ -459,16 +484,28 @@ func (h *htlcTimeoutResolver) handleCommitSpend( } // Let the sweeper sweep the second-level output now that the - // CSV delay has passed. - log.Infof("%T(%x): CSV lock expired, offering second-layer "+ - "output to sweeper: %v", h, h.htlc.RHash[:], op) - - inp := input.NewCsvInput( - op, input.HtlcOfferedTimeoutSecondLevel, - &h.htlcResolution.SweepSignDesc, - h.broadcastHeight, - h.htlcResolution.CsvDelay, - ) + // CSV/CLTV locks have expired. + var inp *input.BaseInput + if h.hasCLTV() { + log.Infof("%T(%x): CSV and CLTV locks expired, offering "+ + "second-layer output to sweeper: %v", h, + h.htlc.RHash[:], op) + inp = input.NewCsvInputWithCltv( + op, input.LeaseHtlcOfferedTimeoutSecondLevel, + &h.htlcResolution.SweepSignDesc, + h.broadcastHeight, h.htlcResolution.CsvDelay, + h.leaseExpiry, + ) + } else { + log.Infof("%T(%x): CSV lock expired, offering "+ + "second-layer output to sweeper: %v", h, + h.htlc.RHash[:], op) + inp = input.NewCsvInput( + op, input.HtlcOfferedTimeoutSecondLevel, + &h.htlcResolution.SweepSignDesc, + h.broadcastHeight, h.htlcResolution.CsvDelay, + ) + } _, err = h.Sweeper.SweepInput( inp, sweep.Params{ @@ -681,6 +718,23 @@ func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) { h.htlc = htlc } +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (h *htlcTimeoutResolver) SupplementState(state *channeldb.OpenChannel) { + if state.ChanType.HasLeaseExpiration() { + h.leaseExpiry = state.ThawHeight + } + h.channelInitiator = state.IsInitiator +} + +// hasCLTV denotes whether the resolver must wait for an additional CLTV to +// expire before resolving the contract. +func (h *htlcTimeoutResolver) hasCLTV() bool { + return h.channelInitiator && h.leaseExpiry > 0 +} + // HtlcPoint returns the htlc's outpoint on the commitment tx. // // NOTE: Part of the htlcContractResolver interface. diff --git a/docs/release-notes/release-notes-0.14.0.md b/docs/release-notes/release-notes-0.14.0.md index ae221939687..8364dd6226d 100644 --- a/docs/release-notes/release-notes-0.14.0.md +++ b/docs/release-notes/release-notes-0.14.0.md @@ -1,5 +1,28 @@ # Release Notes +## Protocol Extensions + +### Explicit Channel Negotiation + +[A new protocol extension has been added known as explicit channel negotiation] +(https://github.com/lightningnetwork/lnd/pull/5373). This allows a channel +initiator to signal their desired channel type to use with the remote peer. If +the remote peer supports said channel type and agrees, the previous implicit +negotiation based on the shared set of feature bits is bypassed, and the +proposed channel type is used. + +### Script Enforced Channel Leases + +[A new channel commitment type that builds upon the recently introduced anchors +commitment format has been introduced to back channel leases resulting from +Lightning Pool](https://github.com/lightningnetwork/lnd/pull/5549). This new +channel commitment type features an additional spend requirement on any +commitment and HTLC outputs that pay directly to the channel initiator. The +channel initiator must now wait for the channel lease maturity, expressed as an +absolute height of the chain, to be met before being able to sweep their funds, +on top of the usual CSV delay requirement. See the linked pull request for more +details. + ## RPC Server * [Return payment address and add index from @@ -83,4 +106,5 @@ to make LNDs payment throughput (and latency) with better when using etcd. * Martin Habovstiak * Zero-1729 * Oliver Gugger +* Wilmer Paulino * Yong Yu diff --git a/feature/default_sets.go b/feature/default_sets.go index 9103f903c0a..d345c90df94 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -60,4 +60,12 @@ var defaultSetDesc = setDesc{ lnwire.AMPRequired: { SetInvoiceAmp: {}, // 9A }, + lnwire.ExplicitChannelTypeOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, + lnwire.ScriptEnforcedLeaseOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, } diff --git a/feature/deps.go b/feature/deps.go index 51ee37e9bfc..92b0c03d59b 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -58,9 +58,17 @@ var deps = depDesc{ lnwire.AnchorsOptional: { lnwire.StaticRemoteKeyOptional: {}, }, + lnwire.AnchorsZeroFeeHtlcTxOptional: { + lnwire.StaticRemoteKeyOptional: {}, + }, lnwire.AMPOptional: { lnwire.PaymentAddrOptional: {}, }, + lnwire.ExplicitChannelTypeOptional: {}, + lnwire.ScriptEnforcedLeaseOptional: { + lnwire.ExplicitChannelTypeOptional: {}, + lnwire.AnchorsZeroFeeHtlcTxOptional: {}, + }, } // ValidateDeps asserts that a feature vector sets all features and their diff --git a/feature/manager.go b/feature/manager.go index 9891807151e..50a54d62516 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -23,6 +23,10 @@ type Config struct { // NoWumbo unsets any bits signalling support for wumbo channels. NoWumbo bool + + // NoScriptEnforcementLease unsets any bits signaling support for script + // enforced leases. + NoScriptEnforcementLease bool } // Manager is responsible for generating feature vectors for different requested @@ -92,6 +96,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.WumboChannelsOptional) raw.Unset(lnwire.WumboChannelsRequired) } + if cfg.NoScriptEnforcementLease { + raw.Unset(lnwire.ScriptEnforcedLeaseOptional) + raw.Unset(lnwire.ScriptEnforcedLeaseRequired) + } // Ensure that all of our feature sets properly set any // dependent features. diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go new file mode 100644 index 00000000000..091c6fcbb63 --- /dev/null +++ b/funding/commitment_type_negotiation.go @@ -0,0 +1,137 @@ +package funding + +import ( + "errors" + + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // errUnsupportedExplicitNegotiation is an error returned when explicit + // channel commitment negotiation is attempted but either peer of the + // channel does not support it. + errUnsupportedExplicitNegotiation = errors.New("explicit channel " + + "type negotiation not supported") + + // errUnsupportedCommitmentType is an error returned when a specific + // channel commitment type is being explicitly negotiated but either + // peer of the channel does not support it. + errUnsupportedChannelType = errors.New("requested channel type " + + "not supported") +) + +// negotiateCommitmentType negotiates the commitment type of a newly opened +// channel. If a channelType is provided, explicit negotiation for said type +// will be attempted if the set of both local and remote features support it. +// Otherwise, implicit negotiation will be attempted. +func negotiateCommitmentType(channelType *lnwire.ChannelType, + local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) { + + if channelType != nil { + if !hasFeatures(local, remote, lnwire.ExplicitChannelTypeOptional) { + return 0, errUnsupportedExplicitNegotiation + } + return explicitNegotiateCommitmentType( + *channelType, local, remote, + ) + } + + return implicitNegotiateCommitmentType(local, remote), nil +} + +// explicitNegotiateCommitmentType attempts to explicitly negotiate for a +// specific channel type. Since the channel type is comprised of a set of even +// feature bits, we also make sure each feature is supported by both peers. An +// error is returned if either peer does not support said channel type. +func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, + local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) { + + channelFeatures := lnwire.RawFeatureVector(channelType) + + switch { + // Lease script enforcement + anchors zero fee + static remote key + // features only. + case channelFeatures.OnlyContains( + lnwire.ScriptEnforcedLeaseRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.StaticRemoteKeyRequired, + ): + if !hasFeatures( + local, remote, + lnwire.ScriptEnforcedLeaseOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.StaticRemoteKeyOptional, + ) { + return 0, errUnsupportedChannelType + } + return lnwallet.CommitmentTypeScriptEnforcedLease, nil + + // Anchors zero fee + static remote key features only. + case channelFeatures.OnlyContains( + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.StaticRemoteKeyRequired, + ): + if !hasFeatures( + local, remote, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.StaticRemoteKeyOptional, + ) { + return 0, errUnsupportedChannelType + } + return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil + + // Static remote key feature only. + case channelFeatures.OnlyContains(lnwire.StaticRemoteKeyRequired): + if !hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) { + return 0, errUnsupportedChannelType + } + return lnwallet.CommitmentTypeTweakless, nil + + // No features, use legacy commitment type. + case channelFeatures.IsEmpty(): + return lnwallet.CommitmentTypeLegacy, nil + + default: + return 0, errUnsupportedChannelType + } +} + +// implicitNegotiateCommitmentType negotiates the commitment type of a channel +// implicitly by choosing the latest type supported by the local and remote +// fetures. +func implicitNegotiateCommitmentType(local, + remote *lnwire.FeatureVector) lnwallet.CommitmentType { + + // If both peers are signalling support for anchor commitments with + // zero-fee HTLC transactions, we'll use this type. + if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) { + return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx + } + + // Since we don't want to support the "legacy" anchor type, we will fall + // back to static remote key if the nodes don't support the zero fee + // HTLC tx anchor type. + // + // If both nodes are signaling the proper feature bit for tweakless + // commitments, we'll use that. + if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) { + return lnwallet.CommitmentTypeTweakless + } + + // Otherwise we'll fall back to the legacy type. + return lnwallet.CommitmentTypeLegacy +} + +// hasFeatures determines whether a set of features is supported by both the set +// of local and remote features. +func hasFeatures(local, remote *lnwire.FeatureVector, + features ...lnwire.FeatureBit) bool { + + for _, feature := range features { + if !local.HasFeature(feature) || !remote.HasFeature(feature) { + return false + } + } + return true +} diff --git a/funding/commitment_type_negotiation_test.go b/funding/commitment_type_negotiation_test.go new file mode 100644 index 00000000000..c9ac25a7a87 --- /dev/null +++ b/funding/commitment_type_negotiation_test.go @@ -0,0 +1,191 @@ +package funding + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +// TestCommitmentTypeNegotiation tests all of the possible paths of a channel +// commitment type negotiation. +func TestCommitmentTypeNegotiation(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + channelFeatures *lnwire.RawFeatureVector + localFeatures *lnwire.RawFeatureVector + remoteFeatures *lnwire.RawFeatureVector + expectsRes lnwallet.CommitmentType + expectsErr error + }{ + { + name: "explicit missing remote negotiation feature", + channelFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + ), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + ), + expectsErr: errUnsupportedExplicitNegotiation, + }, + { + name: "explicit missing remote commitment feature", + channelFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + ), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.ExplicitChannelTypeOptional, + ), + expectsErr: errUnsupportedChannelType, + }, + { + name: "explicit anchors", + channelFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + ), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsErr: nil, + }, + { + name: "explicit tweakless", + channelFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + ), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + expectsRes: lnwallet.CommitmentTypeTweakless, + expectsErr: nil, + }, + { + name: "explicit legacy", + channelFeatures: lnwire.NewRawFeatureVector(), + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + expectsRes: lnwallet.CommitmentTypeLegacy, + expectsErr: nil, + }, + { + name: "implicit anchors", + channelFeatures: nil, + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + lnwire.ExplicitChannelTypeOptional, + ), + expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, + expectsErr: nil, + }, + { + name: "implicit tweakless", + channelFeatures: nil, + localFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + ), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + ), + expectsRes: lnwallet.CommitmentTypeTweakless, + expectsErr: nil, + }, + { + name: "implicit legacy", + channelFeatures: nil, + localFeatures: lnwire.NewRawFeatureVector(), + remoteFeatures: lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyOptional, + lnwire.AnchorsZeroFeeHtlcTxOptional, + ), + expectsRes: lnwallet.CommitmentTypeLegacy, + expectsErr: nil, + }, + } + + for _, testCase := range testCases { + testCase := testCase + ok := t.Run(testCase.name, func(t *testing.T) { + localFeatures := lnwire.NewFeatureVector( + testCase.localFeatures, lnwire.Features, + ) + remoteFeatures := lnwire.NewFeatureVector( + testCase.remoteFeatures, lnwire.Features, + ) + + var channelType *lnwire.ChannelType + if testCase.channelFeatures != nil { + channelType = new(lnwire.ChannelType) + *channelType = lnwire.ChannelType( + *testCase.channelFeatures, + ) + } + localType, err := negotiateCommitmentType( + channelType, localFeatures, remoteFeatures, + ) + require.Equal(t, testCase.expectsErr, err) + + remoteType, err := negotiateCommitmentType( + channelType, remoteFeatures, localFeatures, + ) + require.Equal(t, testCase.expectsErr, err) + + if testCase.expectsErr != nil { + return + } + + require.Equal(t, testCase.expectsRes, localType) + require.Equal(t, testCase.expectsRes, remoteType) + }) + if !ok { + return + } + } +} diff --git a/funding/manager.go b/funding/manager.go index 63269ccb675..4eda6ae5436 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -147,6 +147,10 @@ type reservationWithCtx struct { // maxLocalCsv is the maximum csv we will accept from the remote. maxLocalCsv uint16 + // channelType is the explicit channel type proposed by the initiator of + // the channel. + channelType *lnwire.ChannelType + updateMtx sync.RWMutex lastUpdated time.Time @@ -240,6 +244,11 @@ type InitFundingMsg struct { // protocol. PendingChanID [32]byte + // ChannelType allows the caller to use an explicit channel type for the + // funding negotiation. This type will only be observed if BOTH sides + // support explicit channel type negotiation. + ChannelType *lnwire.ChannelType + // Updates is a channel which updates to the opening status of the channel // are sent on. Updates chan *lnrpc.OpenStatusUpdate @@ -1132,43 +1141,6 @@ func (f *Manager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) { } } -// commitmentType returns the commitment type to use for the channel, based on -// the features the two peers have available. -func commitmentType(localFeatures, - remoteFeatures *lnwire.FeatureVector) lnwallet.CommitmentType { - - // If both peers are signalling support for anchor commitments with - // zero-fee HTLC transactions, we'll use this type. - localZeroFee := localFeatures.HasFeature( - lnwire.AnchorsZeroFeeHtlcTxOptional, - ) - remoteZeroFee := remoteFeatures.HasFeature( - lnwire.AnchorsZeroFeeHtlcTxOptional, - ) - if localZeroFee && remoteZeroFee { - return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx - } - - // Since we don't want to support the "legacy" anchor type, we will - // fall back to static remote key if the nodes don't support the zero - // fee HTLC tx anchor type. - localTweakless := localFeatures.HasFeature( - lnwire.StaticRemoteKeyOptional, - ) - remoteTweakless := remoteFeatures.HasFeature( - lnwire.StaticRemoteKeyOptional, - ) - - // If both nodes are signaling the proper feature bit for tweakless - // copmmitments, we'll use that. - if localTweakless && remoteTweakless { - return lnwallet.CommitmentTypeTweakless - } - - // Otherwise we'll fall back to the legacy type. - return lnwallet.CommitmentTypeLegacy -} - // handleFundingOpen creates an initial 'ChannelReservation' within the wallet, // then responds to the source peer with an accept channel message progressing // the funding workflow. @@ -1309,10 +1281,19 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, // // Before we init the channel, we'll also check to see what commitment // format we can use with this peer. This is dependent on *both* us and - // the remote peer are signaling the proper feature bit. - commitType := commitmentType( - peer.LocalFeatures(), peer.RemoteFeatures(), + // the remote peer are signaling the proper feature bit if we're using + // implicit negotiation, and simply the channel type sent over if we're + // using explicit negotiation. + commitType, err := negotiateCommitmentType( + msg.ChannelType, peer.LocalFeatures(), peer.RemoteFeatures(), ) + if err != nil { + // TODO(roasbeef): should be using soft errors + log.Errorf("channel type negotiation failed: %v", err) + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + chainHash := chainhash.Hash(msg.ChainHash) req := &lnwallet.InitFundingReserveMsg{ ChainHash: &chainHash, @@ -1393,6 +1374,28 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, } reservation.SetOurUpfrontShutdown(shutdown) + // If a script enforced channel lease is being proposed, we'll need to + // validate its custom TLV records. + if commitType == lnwallet.CommitmentTypeScriptEnforcedLease { + if msg.LeaseExpiry == nil { + err := errors.New("missing lease expiry") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + + // If we had a shim registered for this channel prior to + // receiving its corresponding OpenChannel message, then we'll + // validate the proposed LeaseExpiry against what was registered + // in our shim. + if reservation.LeaseExpiry() != 0 { + if uint32(*msg.LeaseExpiry) != reservation.LeaseExpiry() { + err := errors.New("lease expiry mismatch") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + } + } + log.Infof("Requiring %v confirmations for pendingChan(%x): "+ "amt=%v, push_amt=%v, committype=%v, upfrontShutdown=%x", numConfsReq, msg.PendingChannelID, amt, msg.PushAmount, @@ -1442,6 +1445,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, remoteMaxValue: remoteMaxValue, remoteMaxHtlcs: maxHtlcs, maxLocalCsv: f.cfg.MaxLocalCSVDelay, + channelType: msg.ChannelType, err: make(chan error, 1), peer: peer, } @@ -1514,6 +1518,8 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, HtlcPoint: ourContribution.HtlcBasePoint.PubKey, FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, UpfrontShutdownScript: ourContribution.UpfrontShutdown, + ChannelType: msg.ChannelType, + LeaseExpiry: msg.LeaseExpiry, } if err := peer.SendMessage(true, &fundingAccept); err != nil { @@ -1545,6 +1551,45 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer, log.Infof("Recv'd fundingResponse for pending_id(%x)", pendingChanID[:]) + // Perform some basic validation of any custom TLV records included. + // + // TODO: Return errors as funding.Error to give context to remote peer? + if resCtx.channelType != nil { + // We'll want to quickly check that the ChannelType echoed by + // the channel request recipient matches what we proposed. + if msg.ChannelType == nil { + err := errors.New("explicit channel type not echoed back") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + proposedFeatures := lnwire.RawFeatureVector(*resCtx.channelType) + ackedFeatures := lnwire.RawFeatureVector(*msg.ChannelType) + if !proposedFeatures.Equals(&ackedFeatures) { + err := errors.New("channel type mismatch") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + + // We'll want to do the same with the LeaseExpiry if one should + // be set. + if resCtx.reservation.LeaseExpiry() != 0 { + if msg.LeaseExpiry == nil { + err := errors.New("lease expiry not echoed back") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + if uint32(*msg.LeaseExpiry) != resCtx.reservation.LeaseExpiry() { + err := errors.New("lease expiry mismatch") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + } + } else if msg.ChannelType != nil { + err := errors.New("received unexpected channel type") + f.failFundingFlow(peer, msg.PendingChannelID, err) + return + } + // The required number of confirmations should not be greater than the // maximum number of confirmations required by the ChainNotifier to // properly dispatch confirmations. @@ -3178,9 +3223,15 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // Before we init the channel, we'll also check to see what commitment // format we can use with this peer. This is dependent on *both* us and // the remote peer are signaling the proper feature bit. - commitType := commitmentType( - msg.Peer.LocalFeatures(), msg.Peer.RemoteFeatures(), + commitType, err := negotiateCommitmentType( + msg.ChannelType, msg.Peer.LocalFeatures(), + msg.Peer.RemoteFeatures(), ) + if err != nil { + log.Errorf("channel type negotiation failed: %v", err) + msg.Err <- err + return + } // First, we'll query the fee estimator for a fee that should get the // commitment transaction confirmed by the next few blocks (conf target @@ -3194,9 +3245,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // For anchor channels cap the initial commit fee rate at our defined // maximum. - if commitType == lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx && + if commitType.HasAnchors() && commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate { - commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate } @@ -3272,6 +3322,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { remoteMaxValue: maxValue, remoteMaxHtlcs: maxHtlcs, maxLocalCsv: maxCSV, + channelType: msg.ChannelType, reservation: reservation, peer: msg.Peer, updates: msg.Updates, @@ -3292,6 +3343,14 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // remote party. chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit) + // When opening a script enforced channel lease, include the required + // expiry TLV record in our proposal. + var leaseExpiry *lnwire.LeaseExpiry + if commitType == lnwallet.CommitmentTypeScriptEnforcedLease { + leaseExpiry = new(lnwire.LeaseExpiry) + *leaseExpiry = lnwire.LeaseExpiry(reservation.LeaseExpiry()) + } + log.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.Peer.Address(), chanID, commitType) @@ -3315,6 +3374,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, ChannelFlags: channelFlags, UpfrontShutdownScript: shutdown, + ChannelType: msg.ChannelType, + LeaseExpiry: leaseExpiry, } if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { e := fmt.Errorf("unable to send funding request message: %v", diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index cfdadd969ef..10b13324e97 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -134,6 +134,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, channelCapacity := aliceAmount + bobAmount csvTimeoutAlice := uint32(5) csvTimeoutBob := uint32(4) + isAliceInitiator := true aliceConstraints := &channeldb.ChannelConstraints{ DustLimit: btcutil.Amount(200), @@ -230,6 +231,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, + isAliceInitiator, 0, ) if err != nil { return nil, nil, nil, err @@ -298,7 +300,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, IdentityPub: aliceKeyPub, FundingOutpoint: *prevOut, ChanType: channeldb.SingleFunderTweaklessBit, - IsInitiator: true, + IsInitiator: isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: bobCommitPoint, RevocationProducer: alicePreimageProducer, @@ -317,7 +319,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, IdentityPub: bobKeyPub, FundingOutpoint: *prevOut, ChanType: channeldb.SingleFunderTweaklessBit, - IsInitiator: false, + IsInitiator: !isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: aliceCommitPoint, RevocationProducer: bobPreimageProducer, diff --git a/input/input.go b/input/input.go index 87a6339eed8..87d4b9fcd4d 100644 --- a/input/input.go +++ b/input/input.go @@ -90,6 +90,7 @@ type inputKit struct { signDesc SignDescriptor heightHint uint32 blockToMaturity uint32 + cltvExpiry uint32 // unconfParent contains information about a potential unconfirmed // parent transaction. @@ -111,7 +112,7 @@ func (i *inputKit) RequiredTxOut() *wire.TxOut { // must be used in the transaction including it. This will be false for the // base input type since we can re-sign for any lock time. func (i *inputKit) RequiredLockTime() (uint32, bool) { - return 0, false + return i.cltvExpiry, i.cltvExpiry > 0 } // WitnessType returns the type of witness that must be generated to spend the @@ -196,6 +197,25 @@ func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType, } } +// NewCsvInputWithCltv assembles a new csv and cltv locked input that can be +// used to construct a sweep transaction. +func NewCsvInputWithCltv(outpoint *wire.OutPoint, witnessType WitnessType, + signDescriptor *SignDescriptor, heightHint uint32, + csvDelay uint32, cltvExpiry uint32) *BaseInput { + + return &BaseInput{ + inputKit{ + outpoint: *outpoint, + witnessType: witnessType, + signDesc: *signDescriptor, + heightHint: heightHint, + blockToMaturity: csvDelay, + cltvExpiry: cltvExpiry, + unconfParent: nil, + }, + } +} + // CraftInputScript returns a valid set of input scripts allowing this output // to be spent. The returned input scripts should target the input at location // txIndex within the passed transaction. The input scripts generated by this diff --git a/input/script_utils.go b/input/script_utils.go index 7ce6fff53ee..f94af5f8340 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -726,6 +726,78 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, return builder.Script() } +// LeaseSecondLevelHtlcScript is the uniform script that's used as the output for +// the second-level HTLC transactions. The second level transaction acts as a +// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be +// spent in a particular way, and to a particular output. +// +// Possible Input Scripts: +// * To revoke an HTLC output that has been transitioned to the claim+delay +// state: +// * 1 +// +// * To claim an HTLC output, either with a pre-image or due to a timeout: +// * 0 +// +// OP_IF +// +// OP_ELSE +// +// OP_CHECKLOCKTIMEVERIFY +// OP_DROP +// +// OP_CHECKSEQUENCEVERIFY +// OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG +func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, + csvDelay, cltvExpiry uint32) ([]byte, error) { + + builder := txscript.NewScriptBuilder() + + // If this is the revocation clause for this script is to be executed, + // the spender will push a 1, forcing us to hit the true clause of this + // if statement. + builder.AddOp(txscript.OP_IF) + + // If this this is the revocation case, then we'll push the revocation + // public key on the stack. + builder.AddData(revocationKey.SerializeCompressed()) + + // Otherwise, this is either the sender or receiver of the HTLC + // attempting to claim the HTLC output. + builder.AddOp(txscript.OP_ELSE) + + // The channel initiator always has the additional channel lease + // expiration constraint for outputs that pay to them which must be + // satisfied. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // In order to give the other party time to execute the revocation + // clause above, we require a relative timeout to pass before the + // output can be spent. + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // If the relative timelock passes, then we'll add the delay key to the + // stack to ensure that we properly authenticate the spending party. + builder.AddData(delayKey.SerializeCompressed()) + + // Close out the if statement. + builder.AddOp(txscript.OP_ENDIF) + + // In either case, we'll ensure that only either the party possessing + // the revocation private key, or the delay private key is able to + // spend this output. + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() +} + // HtlcSpendSuccess spends a second-level HTLC output. This function is to be // used by the sender of an HTLC to claim the output after a relative timeout // or the receiver of the HTLC to claim on-chain with the pre-image. @@ -889,6 +961,66 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) return builder.Script() } +// LeaseCommitScriptToSelf constructs the public key script for the output on the +// commitment transaction paying to the "owner" of said commitment transaction. +// If the other party learns of the preimage to the revocation hash, then they +// can claim all the settled funds in the channel, plus the unsettled funds. +// +// Possible Input Scripts: +// REVOKE: 1 +// SENDRSWEEP: +// +// Output Script: +// OP_IF +// +// OP_ELSE +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG +func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, + csvTimeout, leaseExpiry uint32) ([]byte, error) { + + // This script is spendable under two conditions: either the + // 'csvTimeout' has passed and we can redeem our funds, or they can + // produce a valid signature with the revocation public key. The + // revocation public key will *only* be known to the other party if we + // have divulged the revocation hash, allowing them to homomorphically + // derive the proper private key which corresponds to the revoke public + // key. + builder := txscript.NewScriptBuilder() + + builder.AddOp(txscript.OP_IF) + + // If a valid signature using the revocation key is presented, then + // allow an immediate spend provided the proper signature. + builder.AddData(revokeKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ELSE) + + // Otherwise, we can re-claim our funds after once the CLTV lease + // maturity has been met, along with the CSV delay of 'csvTimeout' + // timeout blocks, and a valid signature. + builder.AddInt64(int64(leaseExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + builder.AddData(selfKey.SerializeCompressed()) + + builder.AddOp(txscript.OP_ENDIF) + + // Finally, we'll validate the signature against the public key that's + // left on the top of the stack. + builder.AddOp(txscript.OP_CHECKSIG) + + return builder.Script() +} + // CommitSpendTimeout constructs a valid witness allowing the owner of a // particular commitment transaction to spend the output returning settled // funds back to themselves after a relative block timeout. In order to @@ -1033,6 +1165,40 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } +// LeaseCommitScriptToRemoteConfirmed constructs the script for the output on +// the commitment transaction paying to the remote party of said commitment +// transaction. The money can only be spend after one confirmation. +// +// Possible Input Scripts: +// SWEEP: +// +// Output Script: +// OP_CHECKSIGVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// 1 OP_CHECKSEQUENCEVERIFY +func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, + leaseExpiry uint32) ([]byte, error) { + + builder := txscript.NewScriptBuilder() + + // Only the given key can spend the output. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + // The channel initiator always has the additional channel lease + // expiration constraint for outputs that pay to them which must be + // satisfied. + builder.AddInt64(int64(leaseExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + // Check that it has one confirmation. + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() +} + // CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to // spend their settled output on the counterparty's commitment transaction when // it has one confirmetion. This is used for the anchor channel type. The diff --git a/input/script_utils_test.go b/input/script_utils_test.go index 5978d3eec18..e72c8c68c6f 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" ) // assertEngineExecution executes the VM returned by the newEngine closure, @@ -1184,6 +1185,452 @@ func TestSecondLevelHtlcSpends(t *testing.T) { } } +// TestLeaseSecondLevelHtlcSpends tests all the possible redemption clauses from +// the HTLC success and timeout covenant transactions in script enforced lease +// commitments. +func TestLeaseSecondLevelHtlcSpends(t *testing.T) { + t.Parallel() + + // We'll start be creating a creating a 2BTC HTLC. + const htlcAmt = btcutil.Amount(2 * 10e8) + + // In all of our scenarios, the CSV timeout to claim a self output will + // be 5 blocks. + const claimDelay = 5 + + // In all of our scenarios, the CLTV timelock will expire at height + // 1337. + const leaseExpiry = 1337 + + // First we'll set up some initial key state for Alice and Bob that + // will be used in the scripts we created below. + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( + btcec.S256(), testWalletPrivKey, + ) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( + btcec.S256(), bobsPrivKey, + ) + + revokePreimage := testHdSeed.CloneBytes() + commitSecret, commitPoint := btcec.PrivKeyFromBytes( + btcec.S256(), revokePreimage, + ) + + // As we're modeling this as Bob sweeping the HTLC on-chain from his + // commitment transaction after a period of time, we'll be using a + // revocation key derived from Alice's base point and his secret. + revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint) + + // Next, craft a fake HTLC outpoint that we'll use to generate the + // sweeping transaction using. + txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) + require.NoError(t, err) + htlcOutPoint := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(wire.NewTxIn(htlcOutPoint, nil, nil)) + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + // The delay key will be crafted using Bob's public key as the output + // we created will be spending from Alice's commitment transaction. + delayKey := TweakPubKey(bobKeyPub, commitPoint) + + // The commit tweak will be required in order for Bob to derive the + // proper key need to spend the output. + commitTweak := SingleTweakBytes(commitPoint, bobKeyPub) + + // Finally we'll generate the HTLC script itself that we'll be spending + // from. The revocation clause can be claimed by Alice, while Bob can + // sweep the output after a particular delay. + htlcWitnessScript, err := LeaseSecondLevelHtlcScript( + revocationKey, delayKey, claimDelay, leaseExpiry, + ) + require.NoError(t, err) + htlcPkScript, err := WitnessScriptHash(htlcWitnessScript) + require.NoError(t, err) + + htlcOutput := &wire.TxOut{ + PkScript: htlcPkScript, + Value: int64(htlcAmt), + } + + // Finally, we'll create mock signers for both of them based on their + // private keys. This test simplifies a bit and uses the same key as + // the base point for all scripts and derivations. + bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} + aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} + + testCases := []struct { + witness func() wire.TxWitness + valid bool + }{ + { + // Sender of the HTLC attempts to activate the + // revocation clause, but uses the wrong key (fails to + // use the double tweak in this case). + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendRevoke( + aliceSigner, signDesc, sweepTx, + ) + }), + false, + }, + { + // Sender of HTLC activates the revocation clause. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + DoubleTweak: commitSecret, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendRevoke( + aliceSigner, signDesc, sweepTx, + ) + }), + true, + }, + { + // Receiver of the HTLC attempts to sweep, but tries to + // do so pre-maturely with a smaller CSV delay (2 + // blocks instead of 5 blocks), even after the CLTV + // timelock expires. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: commitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendSuccess( + bobSigner, signDesc, sweepTx, claimDelay-3, + ) + }), + false, + }, + { + // Receiver of the HTLC sweeps with the proper CSV delay + // and after the CLTV timelock expires, but uses the + // wrong key (leaves off the single tweak). + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendSuccess( + bobSigner, signDesc, sweepTx, claimDelay, + ) + }), + false, + }, + { + // Receiver of the HTLC sweeps with the proper CSV + // delay, and the correct key, but before the CTLV + // timelock expires. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = 0 + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: commitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendSuccess( + bobSigner, signDesc, sweepTx, claimDelay, + ) + }), + false, + }, + { + // Receiver of the HTLC sweeps with the proper CSV + // delay, and the correct key after the CTLV timelock + // expires. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: commitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return HtlcSpendSuccess( + bobSigner, signDesc, sweepTx, claimDelay, + ) + }), + true, + }, + } + + for i, testCase := range testCases { + sweepTx.TxIn[0].Witness = testCase.witness() + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(htlcPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(htlcAmt)) + } + + assertEngineExecution(t, i, testCase.valid, newEngine) + } +} + +// TestLeaseCommmitSpendToSelf tests all the possible redemption clauses from +// the to_self output in a script enforced lease commitment transaction. +func TestLeaseCommmitSpendToSelf(t *testing.T) { + t.Parallel() + + const ( + outputVal = btcutil.Amount(2 * 10e8) + csvDelay = 5 + leaseExpiry = 1337 + ) + + // Set up some initial key state for Alice and Bob that will be used in + // the scripts we created below. + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( + btcec.S256(), testWalletPrivKey, + ) + bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( + btcec.S256(), bobsPrivKey, + ) + + // We'll have Bob take the revocation path in some cases. + revokePreimage := testHdSeed.CloneBytes() + commitSecret, commitPoint := btcec.PrivKeyFromBytes( + btcec.S256(), revokePreimage, + ) + revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint) + + // Construct the script enforced lease to_self commitment transaction + // output. + txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) + require.NoError(t, err) + commitOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + commitScript, err := LeaseCommitScriptToSelf( + aliceKeyPub, revocationKey, csvDelay, leaseExpiry, + ) + require.NoError(t, err) + commitPkScript, err := WitnessScriptHash(commitScript) + require.NoError(t, err) + + commitOutput := &wire.TxOut{ + PkScript: commitPkScript, + Value: int64(outputVal), + } + + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil)) + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + // Create mock signers for both parties to ensure signatures are + // produced and verified correctly. + aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} + bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} + + testCases := []struct { + witness func() wire.TxWitness + valid bool + }{ + { + // Bob can spend with his revocation key, but not + // without the proper tweak. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendRevoke( + bobSigner, signDesc, sweepTx, + ) + }), + false, + }, + { + // Bob can spend with his revocation key with the proper + // tweak. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + DoubleTweak: commitSecret, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendRevoke( + bobSigner, signDesc, sweepTx, + ) + }), + true, + }, + { + // Alice cannot spend with the proper key before the CSV + // delay and after the CLTV timelock has expired. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + sweepTx.TxIn[0].Sequence = LockTimeToSequence( + false, csvDelay/2, + ) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + DoubleTweak: commitSecret, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendTimeout( + aliceSigner, signDesc, sweepTx, + ) + }), + false, + }, + { + // Alice cannot spend with the proper key after the CSV + // delay and before the CLTV timelock has expired. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = 0 + sweepTx.TxIn[0].Sequence = LockTimeToSequence( + false, csvDelay, + ) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + DoubleTweak: commitSecret, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendTimeout( + aliceSigner, signDesc, sweepTx, + ) + }), + false, + }, + { + // Alice can spend with the proper key after the CSV + // delay and CLTV timelock have expired. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + sweepTx.TxIn[0].Sequence = LockTimeToSequence( + false, csvDelay, + ) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendTimeout( + aliceSigner, signDesc, sweepTx, + ) + }), + true, + }, + } + + for i, testCase := range testCases { + sweepTx.TxIn[0].Witness = testCase.witness() + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(commitPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(outputVal)) + } + + assertEngineExecution(t, i, testCase.valid, newEngine) + } +} + // TestCommitSpendToRemoteConfirmed checks that the delayed version of the // to_remote version can only be spent by the owner, and after one // confirmation. @@ -1291,6 +1738,148 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) { } } +// TestLeaseCommitSpendToRemoteConfirmed checks that the delayed version of the +// to_remote version can only be spent by the owner, after one confirmation, and +// after the lease expiration has been met. +func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) { + t.Parallel() + + const ( + outputVal = btcutil.Amount(2 * 10e8) + leaseExpiry = 1337 + ) + + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( + btcec.S256(), testWalletPrivKey, + ) + + txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) + require.NoError(t, err) + commitOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + commitScript, err := LeaseCommitScriptToRemoteConfirmed( + aliceKeyPub, leaseExpiry, + ) + require.NoError(t, err) + commitPkScript, err := WitnessScriptHash(commitScript) + require.NoError(t, err) + + commitOutput := &wire.TxOut{ + PkScript: commitPkScript, + Value: int64(outputVal), + } + + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil)) + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} + + testCases := []struct { + witness func() wire.TxWitness + valid bool + }{ + { + // Alice can spend after the CSV delay and CLTV timelock + // has passed. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + sweepTx.TxIn[0].Sequence = LockTimeToSequence( + false, 1, + ) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendToRemoteConfirmed( + aliceSigner, signDesc, sweepTx, + ) + }), + true, + }, + { + // Alice cannot spend output without sequence set, even + // once the CLTV timelock has expired. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = leaseExpiry + sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendToRemoteConfirmed( + aliceSigner, signDesc, sweepTx, + ) + }), + false, + }, + { + // Alice cannot spend output without sequence or + // locktime set. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.LockTime = 0 + sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendToRemoteConfirmed( + aliceSigner, signDesc, sweepTx, + ) + }), + false, + }, + } + + for i, testCase := range testCases { + sweepTx.TxIn[0].Witness = testCase.witness() + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine( + commitPkScript, sweepTx, 0, + txscript.StandardVerifyFlags, nil, nil, + int64(outputVal), + ) + } + + assertEngineExecution(t, i, testCase.valid, newEngine) + } +} + // TestSpendAnchor checks that we can spend the anchors using the various spend // paths. func TestSpendAnchor(t *testing.T) { diff --git a/input/size.go b/input/size.go index 6ad55d87a5c..b923f815b8b 100644 --- a/input/size.go +++ b/input/size.go @@ -258,6 +258,17 @@ const ( // - OP_CHECKSIG: 1 byte ToLocalScriptSize = 1 + 1 + 33 + 1 + 1 + 4 + 1 + 1 + 1 + 33 + 1 + 1 + // LeaseWitnessScriptSizeOverhead represents the size overhead in bytes + // of the witness scripts used within script enforced lease commitments. + // This overhead results from the additional CLTV clause required to + // spend. + // + // - OP_DATA: 1 byte + // - lease_expiry: 4 bytes + // - OP_CHECKLOCKTIMEVERIFY: 1 byte + // - OP_DROP: 1 byte + LeaseWitnessScriptSizeOverhead = 1 + 4 + 1 + 1 + // ToLocalTimeoutWitnessSize 156 bytes // - number_of_witness_elements: 1 byte // - local_delay_sig_length: 1 byte diff --git a/input/size_test.go b/input/size_test.go index 8008ac854aa..b2d1e2bee62 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -861,8 +861,8 @@ func TestWitnessSizes(t *testing.T) { func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) { // Create the unsigned timeout tx. timeoutTx, err := lnwallet.CreateHtlcTimeoutTx( - chanType, testOutPoint, testAmt, testCLTVExpiry, - testCSVDelay, testPubkey, testPubkey, + chanType, false, testOutPoint, testAmt, testCLTVExpiry, + testCSVDelay, 0, testPubkey, testPubkey, ) if err != nil { return nil, err @@ -903,7 +903,7 @@ func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) { func genSuccessTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) { // Create the unisgned success tx. successTx, err := lnwallet.CreateHtlcSuccessTx( - chanType, testOutPoint, testAmt, testCSVDelay, + chanType, false, testOutPoint, testAmt, testCSVDelay, 0, testPubkey, testPubkey, ) if err != nil { diff --git a/input/witnessgen.go b/input/witnessgen.go index f5017083c8f..7d0f7fa7313 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -145,6 +145,36 @@ const ( // CommitmentAnchor is a witness that allows us to spend our anchor on // the commitment transaction. CommitmentAnchor StandardWitnessType = 14 + + // LeaseCommitmentTimeLock is a witness that allows us to spend our + // output on our local commitment transaction after a relative and + // absolute lock-time lockout as part of the script enforced lease + // commitment type. + LeaseCommitmentTimeLock StandardWitnessType = 17 + + // LeaseCommitmentToRemoteConfirmed is a witness that allows us to spend + // our output on the counterparty's commitment transaction after a + // confirmation and absolute locktime as part of the script enforced + // lease commitment type. + LeaseCommitmentToRemoteConfirmed StandardWitnessType = 18 + + // LeaseHtlcOfferedTimeoutSecondLevel is a witness that allows us to + // sweep an HTLC output that we extended to a party, but was never + // fulfilled. This HTLC output isn't directly on the commitment + // transaction, but is the result of a confirmed second-level HTLC + // transaction. As a result, we can only spend this after a CSV delay + // and CLTV locktime as part of the script enforced lease commitment + // type. + LeaseHtlcOfferedTimeoutSecondLevel StandardWitnessType = 19 + + // LeaseHtlcAcceptedSuccessSecondLevel is a witness that allows us to + // sweep an HTLC output that was offered to us, and for which we have a + // payment preimage. This HTLC output isn't directly on our commitment + // transaction, but is the result of confirmed second-level HTLC + // transaction. As a result, we can only spend this after a CSV delay + // and CLTV locktime as part of the script enforced lease commitment + // type. + LeaseHtlcAcceptedSuccessSecondLevel StandardWitnessType = 20 ) // String returns a human readable version of the target WitnessType. @@ -203,6 +233,18 @@ func (wt StandardWitnessType) String() string { case NestedWitnessKeyHash: return "NestedWitnessKeyHash" + case LeaseCommitmentTimeLock: + return "LeaseCommitmentTimeLock" + + case LeaseCommitmentToRemoteConfirmed: + return "LeaseCommitmentToRemoteConfirmed" + + case LeaseHtlcOfferedTimeoutSecondLevel: + return "LeaseHtlcOfferedTimeoutSecondLevel" + + case LeaseHtlcAcceptedSuccessSecondLevel: + return "LeaseHtlcAcceptedSuccessSecondLevel" + default: return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt)) } @@ -225,7 +267,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, desc.InputIndex = inputIndex switch wt { - case CommitmentTimeLock: + case CommitmentTimeLock, LeaseCommitmentTimeLock: witness, err := CommitSpendTimeout(signer, desc, tx) if err != nil { return nil, err @@ -235,7 +277,7 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case CommitmentToRemoteConfirmed: + case CommitmentToRemoteConfirmed, LeaseCommitmentToRemoteConfirmed: witness, err := CommitSpendToRemoteConfirmed( signer, desc, tx, ) @@ -307,17 +349,11 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, Witness: witness, }, nil - case HtlcOfferedTimeoutSecondLevel: - witness, err := HtlcSecondLevelSpend(signer, desc, tx) - if err != nil { - return nil, err - } - - return &Script{ - Witness: witness, - }, nil + case HtlcOfferedTimeoutSecondLevel, + LeaseHtlcOfferedTimeoutSecondLevel, + HtlcAcceptedSuccessSecondLevel, + LeaseHtlcAcceptedSuccessSecondLevel: - case HtlcAcceptedSuccessSecondLevel: witness, err := HtlcSecondLevelSpend(signer, desc, tx) if err != nil { return nil, err @@ -382,10 +418,18 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { // to us. case CommitmentTimeLock: return ToLocalTimeoutWitnessSize, false, nil + case LeaseCommitmentTimeLock: + size := ToLocalTimeoutWitnessSize + + LeaseWitnessScriptSizeOverhead + return size, false, nil // 1 CSV time locked output to us on remote commitment. case CommitmentToRemoteConfirmed: return ToRemoteConfirmedWitnessSize, false, nil + case LeaseCommitmentToRemoteConfirmed: + size := ToRemoteConfirmedWitnessSize + + LeaseWitnessScriptSizeOverhead + return size, false, nil // Anchor output on the commitment transaction. case CommitmentAnchor: @@ -396,6 +440,10 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { // sweep. case HtlcOfferedTimeoutSecondLevel: return ToLocalTimeoutWitnessSize, false, nil + case LeaseHtlcOfferedTimeoutSecondLevel: + size := ToLocalTimeoutWitnessSize + + LeaseWitnessScriptSizeOverhead + return size, false, nil // Input to the outgoing HTLC second layer timeout transaction. case HtlcOfferedTimeoutSecondLevelInputConfirmed: @@ -406,6 +454,10 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) { // sweep. case HtlcAcceptedSuccessSecondLevel: return ToLocalTimeoutWitnessSize, false, nil + case LeaseHtlcAcceptedSuccessSecondLevel: + size := ToLocalTimeoutWitnessSize + + LeaseWitnessScriptSizeOverhead + return size, false, nil // Input to the incoming second-layer HTLC success transaction. case HtlcAcceptedSuccessSecondLevelInputConfirmed: diff --git a/lncfg/protocol.go b/lncfg/protocol.go index afc129680fc..23e3d38cf18 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -23,6 +23,11 @@ type ProtocolOptions struct { // NoAnchors should be set if we don't want to support opening or accepting // channels having the anchor commitment type. NoAnchors bool `long:"no-anchors" description:"disable support for anchor commitments"` + + // NoScriptEnforcedLease should be set if we don't want to support + // opening or accepting channels having the script enforced commitment + // type for leased channel. + NoScriptEnforcedLease bool `long:"no-script-enforced-lease" description:"disable support for script enforced lease commitments"` } // Wumbo returns true if lnd should permit the creation and acceptance of wumbo @@ -36,3 +41,9 @@ func (l *ProtocolOptions) Wumbo() bool { func (l *ProtocolOptions) NoAnchorCommitments() bool { return l.NoAnchors } + +// NoScriptEnforcementLease returns true if we have disabled support for the +// script enforcement commitment type for leased channels. +func (l *ProtocolOptions) NoScriptEnforcementLease() bool { + return l.NoScriptEnforcedLease +} diff --git a/lncfg/protocol_rpctest.go b/lncfg/protocol_rpctest.go index 037aec7775e..fb12fd6866d 100644 --- a/lncfg/protocol_rpctest.go +++ b/lncfg/protocol_rpctest.go @@ -23,6 +23,12 @@ type ProtocolOptions struct { // Anchors enables anchor commitments. // TODO(halseth): transition itests to anchors instead! Anchors bool `long:"anchors" description:"enable support for anchor commitments"` + + // ScriptEnforcedLease enables script enforced commitments for channel + // leases. + // + // TODO: Move to experimental? + ScriptEnforcedLease bool `long:"script-enforced-lease" description:"enable support for script enforced lease commitments"` } // Wumbo returns true if lnd should permit the creation and acceptance of wumbo @@ -36,3 +42,9 @@ func (l *ProtocolOptions) Wumbo() bool { func (l *ProtocolOptions) NoAnchorCommitments() bool { return !l.Anchors } + +// NoScriptEnforcementLease returns true if we have disabled support for the +// script enforcement commitment type for leased channels. +func (l *ProtocolOptions) NoScriptEnforcementLease() bool { + return !l.ScriptEnforcedLease +} diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index d4be95e5477..31819c19233 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -80,39 +80,48 @@ func (AddressType) EnumDescriptor() ([]byte, []int) { type CommitmentType int32 const ( + // + //Returned when the commitment type isn't known or unavailable. + CommitmentType_UNKNOWN_COMMITMENT_TYPE CommitmentType = 0 // //A channel using the legacy commitment format having tweaked to_remote //keys. - CommitmentType_LEGACY CommitmentType = 0 + CommitmentType_LEGACY CommitmentType = 1 // //A channel that uses the modern commitment format where the key in the //output of the remote party does not change each state. This makes back //up and recovery easier as when the channel is closed, the funds go //directly to that key. - CommitmentType_STATIC_REMOTE_KEY CommitmentType = 1 + CommitmentType_STATIC_REMOTE_KEY CommitmentType = 2 // //A channel that uses a commitment format that has anchor outputs on the //commitments, allowing fee bumping after a force close transaction has //been broadcast. - CommitmentType_ANCHORS CommitmentType = 2 - // - //Returned when the commitment type isn't known or unavailable. - CommitmentType_UNKNOWN_COMMITMENT_TYPE CommitmentType = 999 + CommitmentType_ANCHORS CommitmentType = 3 + // + //A channel that uses a commitment type that builds upon the anchors + //commitment format, but in addition requires a CLTV clause to spend outputs + //paying to the channel initiator. This is intended for use on leased channels + //to guarantee that the channel initiator has no incentives to close a leased + //channel before its maturity date. + CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4 ) // Enum value maps for CommitmentType. var ( CommitmentType_name = map[int32]string{ - 0: "LEGACY", - 1: "STATIC_REMOTE_KEY", - 2: "ANCHORS", - 999: "UNKNOWN_COMMITMENT_TYPE", + 0: "UNKNOWN_COMMITMENT_TYPE", + 1: "LEGACY", + 2: "STATIC_REMOTE_KEY", + 3: "ANCHORS", + 4: "SCRIPT_ENFORCED_LEASE", } CommitmentType_value = map[string]int32{ - "LEGACY": 0, - "STATIC_REMOTE_KEY": 1, - "ANCHORS": 2, - "UNKNOWN_COMMITMENT_TYPE": 999, + "UNKNOWN_COMMITMENT_TYPE": 0, + "LEGACY": 1, + "STATIC_REMOTE_KEY": 2, + "ANCHORS": 3, + "SCRIPT_ENFORCED_LEASE": 4, } ) @@ -2026,6 +2035,8 @@ type ChannelAcceptRequest struct { // A bit-field which the initiator uses to specify proposed channel // behavior. ChannelFlags uint32 `protobuf:"varint,13,opt,name=channel_flags,json=channelFlags,proto3" json:"channel_flags,omitempty"` + // The commitment type the initiator wishes to use for the proposed channel. + CommitmentType CommitmentType `protobuf:"varint,14,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"` } func (x *ChannelAcceptRequest) Reset() { @@ -2151,6 +2162,13 @@ func (x *ChannelAcceptRequest) GetChannelFlags() uint32 { return 0 } +func (x *ChannelAcceptRequest) GetCommitmentType() CommitmentType { + if x != nil { + return x.CommitmentType + } + return CommitmentType_UNKNOWN_COMMITMENT_TYPE +} + type ChannelAcceptResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4146,7 +4164,7 @@ func (x *Channel) GetCommitmentType() CommitmentType { if x != nil { return x.CommitmentType } - return CommitmentType_LEGACY + return CommitmentType_UNKNOWN_COMMITMENT_TYPE } func (x *Channel) GetLifetime() int64 { @@ -6092,6 +6110,10 @@ type OpenChannelRequest struct { //Max local csv is the maximum csv delay we will allow for our own commitment //transaction. MaxLocalCsv uint32 `protobuf:"varint,17,opt,name=max_local_csv,json=maxLocalCsv,proto3" json:"max_local_csv,omitempty"` + // + //The explicit commitment type to use. Note this field will only be used if + //the remote peer supports explicit channel negotiation. + CommitmentType CommitmentType `protobuf:"varint,18,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"` } func (x *OpenChannelRequest) Reset() { @@ -6247,6 +6269,13 @@ func (x *OpenChannelRequest) GetMaxLocalCsv() uint32 { return 0 } +func (x *OpenChannelRequest) GetCommitmentType() CommitmentType { + if x != nil { + return x.CommitmentType + } + return CommitmentType_UNKNOWN_COMMITMENT_TYPE +} + type OpenStatusUpdate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -14361,7 +14390,7 @@ func (x *PendingChannelsResponse_PendingChannel) GetCommitmentType() CommitmentT if x != nil { return x.CommitmentType } - return CommitmentType_LEGACY + return CommitmentType_UNKNOWN_COMMITMENT_TYPE } type PendingChannelsResponse_PendingOpenChannel struct { @@ -14918,7 +14947,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x4a, 0x04, - 0x08, 0x03, 0x10, 0x04, 0x22, 0xda, 0x03, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x08, 0x03, 0x10, 0x04, 0x22, 0x9a, 0x04, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1d, @@ -14948,7 +14977,11 @@ var file_lightning_proto_rawDesc = []byte{ 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, - 0x73, 0x22, 0xf3, 0x02, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, + 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x22, 0xf3, 0x02, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, @@ -15495,7 +15528,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xc0, 0x05, 0x0a, 0x12, 0x4f, 0x70, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0x80, 0x06, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, @@ -15539,7 +15572,11 @@ var file_lightning_proto_rawDesc = []byte{ 0x63, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x22, 0xf3, 0x01, 0x0a, + 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, 0x0a, 0x0f, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, @@ -16848,362 +16885,364 @@ var file_lightning_proto_rawDesc = []byte{ 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x03, 0x2a, 0x5e, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, - 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, - 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, - 0x53, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, - 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0xe7, - 0x07, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, - 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, - 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, - 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, - 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, - 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, - 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, - 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, - 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, - 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, - 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, - 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, - 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, - 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, - 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, - 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, - 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, - 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, - 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, - 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, - 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, - 0x02, 0x2a, 0xd9, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, - 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, - 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, - 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, - 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, - 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, - 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, - 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, - 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, - 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, - 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, - 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, - 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, - 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, - 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, - 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, - 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, - 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, - 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, - 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, - 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, - 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, - 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, - 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, - 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, - 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, - 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, - 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, - 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, - 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, + 0x03, 0x2a, 0x78, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, + 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, + 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, + 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, + 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, + 0x43, 0x45, 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x2a, 0x61, 0x0a, 0x09, 0x49, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, + 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, + 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, + 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, + 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, + 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, + 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, + 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, + 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, + 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, + 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, + 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, + 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, + 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, + 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, + 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, + 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, + 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xd9, 0x01, 0x0a, 0x14, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, + 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, + 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, + 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, + 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, + 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, + 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, + 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, + 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, + 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, + 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, + 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, + 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, + 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, + 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, + 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, + 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, + 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, + 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, + 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, + 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, + 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, + 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, + 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, - 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, - 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, - 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, - 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, - 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, - 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, - 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, - 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, - 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, - 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, - 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, - 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, - 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, - 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x32, - 0xc9, 0x21, 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, - 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, - 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, - 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, - 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, - 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, - 0x3b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, - 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, - 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x44, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, + 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, + 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, + 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, + 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, + 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, + 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, + 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, + 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, + 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, + 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, + 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x32, 0xc9, 0x21, 0x0a, 0x09, 0x4c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, + 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, + 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, + 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, + 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, 0x6e, + 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, + 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, + 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, + 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, + 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, + 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, + 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, + 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, - 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, - 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, - 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, - 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, - 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, - 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, - 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, - 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, - 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, - 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, - 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, - 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, - 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, - 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, - 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, - 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, - 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, - 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, - 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, - 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, + 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, + 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, + 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, + 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, + 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, + 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, + 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, + 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, - 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, - 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, - 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, - 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, - 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, - 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, - 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, - 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, + 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, + 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, + 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -17435,270 +17474,272 @@ var file_lightning_proto_depIdxs = []int32{ 8, // 5: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit 106, // 6: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route 106, // 7: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route - 184, // 8: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry - 185, // 9: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry - 18, // 10: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 0, // 11: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType - 30, // 12: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress - 49, // 13: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC - 1, // 14: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType - 50, // 15: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints - 50, // 16: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints - 51, // 17: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel - 9, // 18: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType - 2, // 19: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator - 2, // 20: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator - 55, // 21: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution - 3, // 22: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType - 4, // 23: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome - 29, // 24: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint - 54, // 25: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary - 10, // 26: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType - 186, // 27: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry - 59, // 28: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError - 58, // 29: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer - 11, // 30: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType - 68, // 31: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain - 187, // 32: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry - 28, // 33: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint - 28, // 34: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 74, // 35: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate - 71, // 36: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate - 82, // 37: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim - 74, // 38: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate - 70, // 39: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate - 75, // 40: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding - 78, // 41: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator - 28, // 42: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint - 79, // 43: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor - 80, // 44: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim - 81, // 45: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim - 82, // 46: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim - 83, // 47: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel - 84, // 48: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify - 85, // 49: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize - 189, // 50: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel - 192, // 51: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel - 193, // 52: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel - 190, // 53: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel - 51, // 54: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel - 54, // 55: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary - 28, // 56: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint - 28, // 57: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint - 74, // 58: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate - 13, // 59: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType - 194, // 60: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry - 96, // 61: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount - 96, // 62: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount - 96, // 63: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount - 96, // 64: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount - 96, // 65: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount - 96, // 66: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount - 22, // 67: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit - 101, // 68: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator - 100, // 69: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair - 195, // 70: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - 129, // 71: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint - 8, // 72: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit - 106, // 73: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route - 104, // 74: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord - 105, // 75: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord - 196, // 76: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry - 103, // 77: lnrpc.Route.hops:type_name -> lnrpc.Hop - 109, // 78: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode - 112, // 79: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge - 110, // 80: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress - 197, // 81: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry - 111, // 82: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy - 111, // 83: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy - 109, // 84: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode - 112, // 85: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge - 5, // 86: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType - 198, // 87: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - 125, // 88: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate - 126, // 89: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate - 127, // 90: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate - 110, // 91: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress - 199, // 92: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry - 28, // 93: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 111, // 94: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy - 28, // 95: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 128, // 96: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint - 129, // 97: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint - 14, // 98: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState - 131, // 99: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC - 200, // 100: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry - 6, // 101: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState - 201, // 102: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry - 132, // 103: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP - 130, // 104: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice - 15, // 105: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus - 139, // 106: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt - 7, // 107: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason - 16, // 108: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus - 106, // 109: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route - 179, // 110: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure - 138, // 111: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment - 28, // 112: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 129, // 113: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint - 202, // 114: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry - 152, // 115: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport - 28, // 116: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint - 157, // 117: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent - 28, // 118: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint - 28, // 119: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint - 28, // 120: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint - 164, // 121: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups - 161, // 122: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup - 160, // 123: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup - 164, // 124: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups - 169, // 125: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission - 169, // 126: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission - 203, // 127: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry - 17, // 128: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode - 180, // 129: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate - 182, // 130: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op - 150, // 131: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature - 150, // 132: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature - 2, // 133: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator - 1, // 134: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType - 188, // 135: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 188, // 136: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 191, // 137: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments - 188, // 138: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 188, // 139: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 88, // 140: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC - 12, // 141: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - 93, // 142: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance - 150, // 143: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature - 117, // 144: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric - 150, // 145: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature - 150, // 146: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature - 150, // 147: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature - 176, // 148: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList - 94, // 149: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest - 97, // 150: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest - 20, // 151: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest - 31, // 152: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest - 35, // 153: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest - 37, // 154: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest - 20, // 155: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest - 33, // 156: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest - 39, // 157: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest - 41, // 158: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest - 43, // 159: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest - 45, // 160: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest - 47, // 161: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest - 60, // 162: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest - 62, // 163: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription - 64, // 164: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest - 66, // 165: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest - 89, // 166: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest - 52, // 167: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest - 91, // 168: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription - 56, // 169: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest - 76, // 170: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest - 76, // 171: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest - 86, // 172: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg - 27, // 173: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse - 72, // 174: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest - 144, // 175: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest - 23, // 176: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest - 23, // 177: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest - 25, // 178: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest - 25, // 179: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest - 130, // 180: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice - 135, // 181: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest - 134, // 182: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash - 137, // 183: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription - 148, // 184: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString - 140, // 185: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest - 142, // 186: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest - 113, // 187: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest - 115, // 188: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest - 118, // 189: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest - 107, // 190: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest - 99, // 191: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest - 119, // 192: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest - 121, // 193: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest - 123, // 194: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription - 146, // 195: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest - 151, // 196: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest - 154, // 197: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest - 156, // 198: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest - 159, // 199: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest - 162, // 200: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest - 163, // 201: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot - 165, // 202: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest - 167, // 203: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription - 170, // 204: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest - 172, // 205: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest - 174, // 206: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest - 177, // 207: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest - 95, // 208: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse - 98, // 209: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse - 21, // 210: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails - 32, // 211: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse - 36, // 212: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse - 38, // 213: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse - 19, // 214: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction - 34, // 215: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse - 40, // 216: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse - 42, // 217: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse - 44, // 218: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse - 46, // 219: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse - 48, // 220: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse - 61, // 221: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse - 63, // 222: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent - 65, // 223: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse - 67, // 224: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse - 90, // 225: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse - 53, // 226: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse - 92, // 227: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate - 57, // 228: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse - 28, // 229: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint - 77, // 230: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate - 87, // 231: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp - 26, // 232: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest - 73, // 233: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate - 145, // 234: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse - 24, // 235: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse - 24, // 236: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse - 24, // 237: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse - 24, // 238: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse - 133, // 239: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse - 136, // 240: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse - 130, // 241: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice - 130, // 242: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice - 149, // 243: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq - 141, // 244: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse - 143, // 245: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse - 114, // 246: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph - 116, // 247: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse - 112, // 248: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge - 108, // 249: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo - 102, // 250: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse - 120, // 251: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo - 122, // 252: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse - 124, // 253: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate - 147, // 254: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse - 153, // 255: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse - 155, // 256: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse - 158, // 257: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse - 160, // 258: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup - 163, // 259: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 168, // 260: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse - 166, // 261: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse - 163, // 262: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 171, // 263: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse - 173, // 264: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse - 175, // 265: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse - 178, // 266: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse - 208, // [208:267] is the sub-list for method output_type - 149, // [149:208] is the sub-list for method input_type - 149, // [149:149] is the sub-list for extension type_name - 149, // [149:149] is the sub-list for extension extendee - 0, // [0:149] is the sub-list for field type_name + 1, // 8: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType + 184, // 9: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry + 185, // 10: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry + 18, // 11: lnrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 0, // 12: lnrpc.NewAddressRequest.type:type_name -> lnrpc.AddressType + 30, // 13: lnrpc.ConnectPeerRequest.addr:type_name -> lnrpc.LightningAddress + 49, // 14: lnrpc.Channel.pending_htlcs:type_name -> lnrpc.HTLC + 1, // 15: lnrpc.Channel.commitment_type:type_name -> lnrpc.CommitmentType + 50, // 16: lnrpc.Channel.local_constraints:type_name -> lnrpc.ChannelConstraints + 50, // 17: lnrpc.Channel.remote_constraints:type_name -> lnrpc.ChannelConstraints + 51, // 18: lnrpc.ListChannelsResponse.channels:type_name -> lnrpc.Channel + 9, // 19: lnrpc.ChannelCloseSummary.close_type:type_name -> lnrpc.ChannelCloseSummary.ClosureType + 2, // 20: lnrpc.ChannelCloseSummary.open_initiator:type_name -> lnrpc.Initiator + 2, // 21: lnrpc.ChannelCloseSummary.close_initiator:type_name -> lnrpc.Initiator + 55, // 22: lnrpc.ChannelCloseSummary.resolutions:type_name -> lnrpc.Resolution + 3, // 23: lnrpc.Resolution.resolution_type:type_name -> lnrpc.ResolutionType + 4, // 24: lnrpc.Resolution.outcome:type_name -> lnrpc.ResolutionOutcome + 29, // 25: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint + 54, // 26: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary + 10, // 27: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType + 186, // 28: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry + 59, // 29: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError + 58, // 30: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer + 11, // 31: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType + 68, // 32: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain + 187, // 33: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry + 28, // 34: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint + 28, // 35: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 74, // 36: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate + 71, // 37: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate + 82, // 38: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim + 1, // 39: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType + 74, // 40: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate + 70, // 41: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate + 75, // 42: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding + 78, // 43: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator + 28, // 44: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint + 79, // 45: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor + 80, // 46: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim + 81, // 47: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim + 82, // 48: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim + 83, // 49: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel + 84, // 50: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify + 85, // 51: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize + 189, // 52: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel + 192, // 53: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel + 193, // 54: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel + 190, // 55: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel + 51, // 56: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel + 54, // 57: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary + 28, // 58: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint + 28, // 59: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint + 74, // 60: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate + 13, // 61: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType + 194, // 62: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry + 96, // 63: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount + 96, // 64: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount + 96, // 65: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount + 96, // 66: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount + 96, // 67: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount + 96, // 68: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount + 22, // 69: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit + 101, // 70: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator + 100, // 71: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair + 195, // 72: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + 129, // 73: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint + 8, // 74: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit + 106, // 75: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route + 104, // 76: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord + 105, // 77: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord + 196, // 78: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry + 103, // 79: lnrpc.Route.hops:type_name -> lnrpc.Hop + 109, // 80: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode + 112, // 81: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge + 110, // 82: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress + 197, // 83: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry + 111, // 84: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy + 111, // 85: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy + 109, // 86: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode + 112, // 87: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge + 5, // 88: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType + 198, // 89: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + 125, // 90: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate + 126, // 91: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate + 127, // 92: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate + 110, // 93: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress + 199, // 94: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry + 28, // 95: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 111, // 96: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy + 28, // 97: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 128, // 98: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint + 129, // 99: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint + 14, // 100: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState + 131, // 101: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC + 200, // 102: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry + 6, // 103: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState + 201, // 104: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry + 132, // 105: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP + 130, // 106: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice + 15, // 107: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus + 139, // 108: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt + 7, // 109: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason + 16, // 110: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus + 106, // 111: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route + 179, // 112: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure + 138, // 113: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment + 28, // 114: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 129, // 115: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint + 202, // 116: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry + 152, // 117: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport + 28, // 118: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint + 157, // 119: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent + 28, // 120: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint + 28, // 121: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint + 28, // 122: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint + 164, // 123: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups + 161, // 124: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup + 160, // 125: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup + 164, // 126: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups + 169, // 127: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission + 169, // 128: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission + 203, // 129: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry + 17, // 130: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode + 180, // 131: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate + 182, // 132: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op + 150, // 133: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature + 150, // 134: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature + 2, // 135: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator + 1, // 136: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType + 188, // 137: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 188, // 138: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 191, // 139: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments + 188, // 140: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 188, // 141: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 88, // 142: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC + 12, // 143: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + 93, // 144: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance + 150, // 145: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature + 117, // 146: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric + 150, // 147: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature + 150, // 148: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature + 150, // 149: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature + 176, // 150: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList + 94, // 151: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest + 97, // 152: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest + 20, // 153: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest + 31, // 154: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest + 35, // 155: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest + 37, // 156: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest + 20, // 157: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest + 33, // 158: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest + 39, // 159: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest + 41, // 160: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest + 43, // 161: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest + 45, // 162: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest + 47, // 163: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest + 60, // 164: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest + 62, // 165: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription + 64, // 166: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest + 66, // 167: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest + 89, // 168: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest + 52, // 169: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest + 91, // 170: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription + 56, // 171: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest + 76, // 172: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest + 76, // 173: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest + 86, // 174: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg + 27, // 175: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse + 72, // 176: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest + 144, // 177: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest + 23, // 178: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest + 23, // 179: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest + 25, // 180: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest + 25, // 181: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest + 130, // 182: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice + 135, // 183: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest + 134, // 184: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash + 137, // 185: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription + 148, // 186: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString + 140, // 187: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest + 142, // 188: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest + 113, // 189: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest + 115, // 190: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest + 118, // 191: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest + 107, // 192: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest + 99, // 193: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest + 119, // 194: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest + 121, // 195: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest + 123, // 196: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription + 146, // 197: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest + 151, // 198: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest + 154, // 199: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest + 156, // 200: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest + 159, // 201: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest + 162, // 202: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest + 163, // 203: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot + 165, // 204: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest + 167, // 205: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription + 170, // 206: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest + 172, // 207: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest + 174, // 208: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest + 177, // 209: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest + 95, // 210: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse + 98, // 211: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse + 21, // 212: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails + 32, // 213: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse + 36, // 214: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse + 38, // 215: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse + 19, // 216: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction + 34, // 217: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse + 40, // 218: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse + 42, // 219: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse + 44, // 220: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse + 46, // 221: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse + 48, // 222: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse + 61, // 223: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse + 63, // 224: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent + 65, // 225: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse + 67, // 226: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse + 90, // 227: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse + 53, // 228: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse + 92, // 229: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate + 57, // 230: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse + 28, // 231: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint + 77, // 232: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate + 87, // 233: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp + 26, // 234: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest + 73, // 235: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate + 145, // 236: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse + 24, // 237: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse + 24, // 238: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse + 24, // 239: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse + 24, // 240: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse + 133, // 241: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse + 136, // 242: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse + 130, // 243: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice + 130, // 244: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice + 149, // 245: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq + 141, // 246: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse + 143, // 247: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse + 114, // 248: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph + 116, // 249: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse + 112, // 250: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge + 108, // 251: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo + 102, // 252: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse + 120, // 253: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo + 122, // 254: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse + 124, // 255: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate + 147, // 256: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse + 153, // 257: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse + 155, // 258: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse + 158, // 259: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse + 160, // 260: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup + 163, // 261: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 168, // 262: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse + 166, // 263: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse + 163, // 264: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 171, // 265: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse + 173, // 266: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse + 175, // 267: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse + 178, // 268: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse + 210, // [210:269] is the sub-list for method output_type + 151, // [151:210] is the sub-list for method input_type + 151, // [151:151] is the sub-list for extension type_name + 151, // [151:151] is the sub-list for extension extendee + 0, // [0:151] is the sub-list for field type_name } func init() { file_lightning_proto_init() } diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 0a59c6c13e5..686722609c9 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -792,6 +792,9 @@ message ChannelAcceptRequest { // A bit-field which the initiator uses to specify proposed channel // behavior. uint32 channel_flags = 13; + + // The commitment type the initiator wishes to use for the proposed channel. + CommitmentType commitment_type = 14; } message ChannelAcceptResponse { @@ -1115,11 +1118,16 @@ message HTLC { } enum CommitmentType { + /* + Returned when the commitment type isn't known or unavailable. + */ + UNKNOWN_COMMITMENT_TYPE = 0; + /* A channel using the legacy commitment format having tweaked to_remote keys. */ - LEGACY = 0; + LEGACY = 1; /* A channel that uses the modern commitment format where the key in the @@ -1127,19 +1135,23 @@ enum CommitmentType { up and recovery easier as when the channel is closed, the funds go directly to that key. */ - STATIC_REMOTE_KEY = 1; + STATIC_REMOTE_KEY = 2; /* A channel that uses a commitment format that has anchor outputs on the commitments, allowing fee bumping after a force close transaction has been broadcast. */ - ANCHORS = 2; + ANCHORS = 3; /* - Returned when the commitment type isn't known or unavailable. + A channel that uses a commitment type that builds upon the anchors + commitment format, but in addition requires a CLTV clause to spend outputs + paying to the channel initiator. This is intended for use on leased channels + to guarantee that the channel initiator has no incentives to close a leased + channel before its maturity date. */ - UNKNOWN_COMMITMENT_TYPE = 999; + SCRIPT_ENFORCED_LEASE = 4; } message ChannelConstraints { @@ -1862,6 +1874,12 @@ message OpenChannelRequest { transaction. */ uint32 max_local_csv = 17; + + /* + The explicit commitment type to use. Note this field will only be used if + the remote peer supports explicit channel negotiation. + */ + CommitmentType commitment_type = 18; } message OpenStatusUpdate { oneof update { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 5dcb04bd72b..ec7d3277f71 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -2997,6 +2997,10 @@ "type": "integer", "format": "int64", "description": "A bit-field which the initiator uses to specify proposed channel\nbehavior." + }, + "commitment_type": { + "$ref": "#/definitions/lnrpcCommitmentType", + "description": "The commitment type the initiator wishes to use for the proposed channel." } } }, @@ -3498,13 +3502,14 @@ "lnrpcCommitmentType": { "type": "string", "enum": [ + "UNKNOWN_COMMITMENT_TYPE", "LEGACY", "STATIC_REMOTE_KEY", "ANCHORS", - "UNKNOWN_COMMITMENT_TYPE" + "SCRIPT_ENFORCED_LEASE" ], - "default": "LEGACY", - "description": " - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable." + "default": "UNKNOWN_COMMITMENT_TYPE", + "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date." }, "lnrpcConnectPeerRequest": { "type": "object", @@ -4919,6 +4924,10 @@ "type": "integer", "format": "int64", "description": "Max local csv is the maximum csv delay we will allow for our own commitment\ntransaction." + }, + "commitment_type": { + "$ref": "#/definitions/lnrpcCommitmentType", + "description": "The explicit commitment type to use. Note this field will only be used if\nthe remote peer supports explicit channel negotiation." } } }, diff --git a/lntest/harness.go b/lntest/harness.go index ae31ccf04fe..0977930dcab 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -968,6 +968,10 @@ type OpenChannelParams struct { // SatPerVByte is the amount of satoshis to spend in chain fees per virtual // byte of the transaction. SatPerVByte btcutil.Amount + + // CommitmentType is the commitment type that should be used for the + // channel to be opened. + CommitmentType lnrpc.CommitmentType } // OpenChannel attempts to open a channel between srcNode and destNode with the @@ -1006,6 +1010,7 @@ func (n *NetworkHarness) OpenChannel(ctx context.Context, RemoteMaxHtlcs: uint32(p.RemoteMaxHtlcs), FundingShim: p.FundingShim, SatPerByte: int64(p.SatPerVByte), + CommitmentType: p.CommitmentType, } respStream, err := srcNode.OpenChannel(ctx, openReq) diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index def13577d2a..d06c54775a1 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -1260,7 +1260,7 @@ func assertNumPendingChannels(t *harnessTest, node *lntest.HarnessNode, func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, carol *lntest.HarnessNode, carolStartingBalance int64, dave *lntest.HarnessNode, daveStartingBalance int64, - anchors bool) { + commitType lnrpc.CommitmentType) { // Increase the fee estimate so that the following force close tx will // be cpfp'ed. @@ -1275,7 +1275,7 @@ func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, // Upon reconnection, the nodes should detect that Dave is out of sync. // Carol should force close the channel using her latest commitment. expectedTxes := 1 - if anchors { + if commitTypeHasAnchors(commitType) { expectedTxes = 2 } _, err := waitForNTxsInMempool( @@ -1302,14 +1302,6 @@ func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, // Generate a single block, which should confirm the closing tx. _ = mineBlocks(t, net, 1, expectedTxes)[0] - // Dave should sweep his funds immediately, as they are not timelocked. - // We also expect Dave to sweep his anchor, if present. - - _, err = waitForNTxsInMempool( - net.Miner.Client, expectedTxes, minerMempoolTimeout, - ) - require.NoError(t.t, err, "unable to find Dave's sweep tx in mempool") - // Dave should consider the channel pending force close (since he is // waiting for his sweep to confirm). assertNumPendingChannels(t, dave, 0, 1) @@ -1318,11 +1310,93 @@ func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, // before she can sweep her outputs. assertNumPendingChannels(t, carol, 0, 1) - // Mine the sweep tx. - _ = mineBlocks(t, net, 1, expectedTxes)[0] + if commitType == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Dave should sweep his anchor only, since he still has the + // lease CLTV constraint on his commitment output. + _, err = waitForNTxsInMempool( + net.Miner.Client, 1, minerMempoolTimeout, + ) + require.NoError(t.t, err, "unable to find Dave's anchor sweep "+ + "tx in mempool") + + // Mine Dave's anchor sweep tx. + _ = mineBlocks(t, net, 1, 1)[0] + + // After Carol's output matures, she should also reclaim her + // funds. + // + // The commit sweep resolver publishes the sweep tx at + // defaultCSV-1 and we already mined one block after the + // commitmment was published, so take that into account. + mineBlocks(t, net, defaultCSV-1-1, 0) + carolSweep, err := waitForTxInMempool( + net.Miner.Client, minerMempoolTimeout, + ) + require.NoError(t.t, err, "unable to find Carol's sweep tx in "+ + "mempool") + block := mineBlocks(t, net, 1, 1)[0] + assertTxInBlock(t, block, carolSweep) + + // Now the channel should be fully closed also from Carol's POV. + assertNumPendingChannels(t, carol, 0, 0) + + // We'll now mine the remaining blocks to prompt Dave to sweep + // his CLTV-constrained output. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + resp, err := dave.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + blocksTilMaturity := + resp.PendingForceClosingChannels[0].BlocksTilMaturity + require.Positive(t.t, blocksTilMaturity) + + mineBlocks(t, net, uint32(blocksTilMaturity), 0) + daveSweep, err := waitForTxInMempool( + net.Miner.Client, minerMempoolTimeout, + ) + require.NoError(t.t, err, "unable to find Dave's sweep tx in "+ + "mempool") + block = mineBlocks(t, net, 1, 1)[0] + assertTxInBlock(t, block, daveSweep) - // Now Dave should consider the channel fully closed. - assertNumPendingChannels(t, dave, 0, 0) + // Now Dave should consider the channel fully closed. + assertNumPendingChannels(t, dave, 0, 0) + } else { + // Dave should sweep his funds immediately, as they are not + // timelocked. We also expect Dave to sweep his anchor, if + // present. + _, err = waitForNTxsInMempool( + net.Miner.Client, expectedTxes, minerMempoolTimeout, + ) + require.NoError(t.t, err, "unable to find Dave's sweep tx in "+ + "mempool") + + // Mine the sweep tx. + _ = mineBlocks(t, net, 1, expectedTxes)[0] + + // Now Dave should consider the channel fully closed. + assertNumPendingChannels(t, dave, 0, 0) + + // After Carol's output matures, she should also reclaim her + // funds. + // + // The commit sweep resolver publishes the sweep tx at + // defaultCSV-1 and we already mined one block after the + // commitmment was published, so take that into account. + mineBlocks(t, net, defaultCSV-1-1, 0) + carolSweep, err := waitForTxInMempool( + net.Miner.Client, minerMempoolTimeout, + ) + require.NoError(t.t, err, "unable to find Carol's sweep tx in "+ + "mempool") + block := mineBlocks(t, net, 1, 1)[0] + assertTxInBlock(t, block, carolSweep) + + // Now the channel should be fully closed also from Carol's POV. + assertNumPendingChannels(t, carol, 0, 0) + } // We query Dave's balance to make sure it increased after the channel // closed. This checks that he was able to sweep the funds he had in @@ -1337,22 +1411,6 @@ func assertDLPExecuted(net *lntest.NetworkHarness, t *harnessTest, t.t, daveBalance, daveStartingBalance, "balance not increased", ) - // After the Carol's output matures, she should also reclaim her funds. - // - // The commit sweep resolver publishes the sweep tx at defaultCSV-1 and - // we already mined one block after the commitmment was published, so - // take that into account. - mineBlocks(t, net, defaultCSV-1-1, 0) - carolSweep, err := waitForTxInMempool( - net.Miner.Client, minerMempoolTimeout, - ) - require.NoError(t.t, err, "unable to find Carol's sweep tx in mempool") - block := mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, carolSweep) - - // Now the channel should be fully closed also from Carol's POV. - assertNumPendingChannels(t, carol, 0, 0) - // Make sure Carol got her balance back. err = wait.NoError(func() error { ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -1576,17 +1634,18 @@ func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { } func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { + timeout time.Duration, inputs ...wire.OutPoint) chainhash.Hash { - tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) + tx := getSpendingTxInMempool(t, miner, timeout, inputs...) return tx.TxHash() } // getSpendingTxInMempool waits for a transaction spending the given outpoint to // appear in the mempool and returns that tx in full. func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { + timeout time.Duration, inputs ...wire.OutPoint) *wire.MsgTx { + inputSet := make(map[wire.OutPoint]struct{}, len(inputs)) breakTimeout := time.After(timeout) ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() @@ -1606,13 +1665,30 @@ func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, for _, txid := range mempool { tx, err := miner.GetRawTransaction(txid) require.NoError(t.t, err, "unable to fetch tx") - msgTx := tx.MsgTx() + + // Include the inputs again in case they were + // removed in a previous iteration. + for _, input := range inputs { + inputSet[input] = struct{}{} + } + for _, txIn := range msgTx.TxIn { - if txIn.PreviousOutPoint == chanPoint { - return msgTx + input := txIn.PreviousOutPoint + if _, ok := inputSet[input]; ok { + delete(inputSet, input) } } + + if len(inputSet) > 0 { + // Missing input, check next transaction + // or try again. + continue + } + + // Transaction spends all expected inputs, + // return. + return msgTx } } } diff --git a/lntest/itest/lnd_channel_backup_test.go b/lntest/itest/lnd_channel_backup_test.go index 89558dcb35c..73ace1533af 100644 --- a/lntest/itest/lnd_channel_backup_test.go +++ b/lntest/itest/lnd_channel_backup_test.go @@ -336,10 +336,37 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) { // Restore the backup from the on-disk file, using the RPC // interface, for anchor commitment channels. { - name: "restore from backup file anchors", - initiator: true, - private: false, - anchorCommit: true, + name: "restore from backup file anchors", + initiator: true, + private: false, + commitmentType: lnrpc.CommitmentType_ANCHORS, + restoreMethod: func(oldNode *lntest.HarnessNode, + backupFilePath string, + mnemonic []string) (nodeRestorer, error) { + + // Read the entire Multi backup stored within + // this node's channels.backup file. + multi, err := ioutil.ReadFile(backupFilePath) + if err != nil { + return nil, err + } + + // Now that we have Dave's backup file, we'll + // create a new nodeRestorer that will restore + // using the on-disk channels.backup. + return chanRestoreViaRPC( + net, password, mnemonic, multi, oldNode, + ) + }, + }, + + // Restore the backup from the on-disk file, using the RPC + // interface, for script-enforced leased channels. + { + name: "restore from backup file script enforced lease", + initiator: true, + private: false, + commitmentType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, restoreMethod: func(oldNode *lntest.HarnessNode, backupFilePath string, mnemonic []string) (nodeRestorer, error) { @@ -785,9 +812,9 @@ type chanRestoreTestCase struct { // confirmed or not. unconfirmed bool - // anchorCommit is true, then the new anchor commitment type will be - // used for the channels created in the test. - anchorCommit bool + // commitmentType specifies the commitment type that should be used for + // the channel from Dave to Carol. + commitmentType lnrpc.CommitmentType // legacyRevocation signals if a channel with the legacy revocation // producer format should also be created before restoring. @@ -821,8 +848,9 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness, "--minbackoff=50ms", "--maxbackoff=1s", } - if testCase.anchorCommit { - nodeArgs = append(nodeArgs, commitTypeAnchors.Args()...) + if testCase.commitmentType != lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE { + args := nodeArgsForCommitType(testCase.commitmentType) + nodeArgs = append(nodeArgs, args...) } // First, we'll create a brand new node we'll use within the test. If @@ -896,13 +924,25 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness, ) default: + var fundingShim *lnrpc.FundingShim + if testCase.commitmentType == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + _, minerHeight, err := net.Miner.Client.GetBestBlock() + require.NoError(t.t, err) + thawHeight := uint32(minerHeight + 144) + + fundingShim, _, _ = deriveFundingShim( + net, t, from, to, chanAmt, thawHeight, true, + ) + } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) chanPoint := openChannelAndAssert( ctxt, t, net, from, to, lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - Private: testCase.private, + Amt: chanAmt, + PushAmt: pushAmt, + Private: testCase.private, + FundingShim: fundingShim, + CommitmentType: testCase.commitmentType, }, ) @@ -1089,7 +1129,7 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness, // end of the protocol. assertDLPExecuted( net, t, carol, carolStartingBalance, dave, daveStartingBalance, - testCase.anchorCommit, + testCase.commitmentType, ) } diff --git a/lntest/itest/lnd_channel_balance_test.go b/lntest/itest/lnd_channel_balance_test.go index 14552fd5ae6..a4760a8e522 100644 --- a/lntest/itest/lnd_channel_balance_test.go +++ b/lntest/itest/lnd_channel_balance_test.go @@ -83,10 +83,10 @@ func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { // As this is a single funder channel, Alice's balance should be // exactly 0.5 BTC since now state transitions have taken place yet. - checkChannelBalance(net.Alice, amount-cType.calcStaticFee(0), 0) + checkChannelBalance(net.Alice, amount-calcStaticFee(cType, 0), 0) // Ensure Bob currently has no available balance within the channel. - checkChannelBalance(net.Bob, 0, amount-cType.calcStaticFee(0)) + checkChannelBalance(net.Bob, 0, amount-calcStaticFee(cType, 0)) // Finally close the channel between Alice and Bob, asserting that the // channel has been properly closed on-chain. @@ -179,11 +179,11 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { // Check alice's channel balance, which should have zero remote and zero // pending balance. - checkChannelBalance(net.Alice, chanAmt-cType.calcStaticFee(0), 0, 0, 0) + checkChannelBalance(net.Alice, chanAmt-calcStaticFee(cType, 0), 0, 0, 0) // Check carol's channel balance, which should have zero local and zero // pending balance. - checkChannelBalance(carol, 0, chanAmt-cType.calcStaticFee(0), 0, 0) + checkChannelBalance(carol, 0, chanAmt-calcStaticFee(cType, 0), 0, 0) // Channel should be ready for payments. const ( @@ -264,7 +264,7 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) { // Check alice's channel balance, which should have a remote unsettled // balance that equals to the amount of invoices * payAmt. The remote // balance remains zero. - aliceLocal := chanAmt - cType.calcStaticFee(0) - numInvoices*payAmt + aliceLocal := chanAmt - calcStaticFee(cType, 0) - numInvoices*payAmt checkChannelBalance(net.Alice, aliceLocal, 0, 0, numInvoices*payAmt) // Check carol's channel balance, which should have a local unsettled diff --git a/lntest/itest/lnd_channel_force_close.go b/lntest/itest/lnd_channel_force_close.go index 9077a7c4829..bdc99eaab5a 100644 --- a/lntest/itest/lnd_channel_force_close.go +++ b/lntest/itest/lnd_channel_force_close.go @@ -79,7 +79,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, setupNode := func(name string) *lntest.HarnessNode { // Create the node. args := []string{"--hodl.exit-settle"} - args = append(args, commitTypeAnchors.Args()...) + args = append(args, nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)...) node := net.NewNode(t.t, name, args) // Send some coins to the node. @@ -240,9 +240,9 @@ func calculateTxnsFeeRate(t *testing.T, func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { // We'll test the scenario for some of the commitment types, to ensure // outputs can be swept. - commitTypes := []commitType{ - commitTypeLegacy, - commitTypeAnchors, + commitTypes := []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_ANCHORS, } for _, channelType := range commitTypes { @@ -257,7 +257,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { success := t.t.Run(testName, func(t *testing.T) { ht := newHarnessTest(t, net) - args := channelType.Args() + args := nodeArgsForCommitType(channelType) alice := net.NewNode(ht.t, "Alice", args) defer shutdownAndAssert(net, ht, alice) @@ -291,7 +291,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { } func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, - alice, carol *lntest.HarnessNode, channelType commitType) { + alice, carol *lntest.HarnessNode, channelType lnrpc.CommitmentType) { ctxb := context.Background() @@ -405,7 +405,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // sweep the HTLC second level output one block earlier (than the // nursery that waits an additional block, and handles non-anchor // channels). So we set a maturity height that is one less. - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { htlcCsvMaturityHeight = padCLTV( startHeight + defaultCLTV + defaultCSV, ) @@ -489,7 +489,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // also expect the anchor sweep tx to be in the mempool. expectedTxes := 1 expectedFeeRate := commitFeeRate - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { expectedTxes = 2 expectedFeeRate = actualFeeRate } @@ -526,7 +526,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // If we expect anchors, add alice's anchor to our expected set of // reports. - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { aliceReports[aliceAnchor.OutPoint.String()] = &lnrpc.Resolution{ ResolutionType: lnrpc.ResolutionType_ANCHOR, Outcome: lnrpc.ResolutionOutcome_CLAIMED, @@ -583,7 +583,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, "limbo") } expectedRecoveredBalance := int64(0) - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { expectedRecoveredBalance = anchorSize } if forceClose.RecoveredBalance != expectedRecoveredBalance { @@ -632,7 +632,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, ) // If we have anchors, add an anchor resolution for carol. - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { carolReports[carolAnchor.OutPoint.String()] = &lnrpc.Resolution{ ResolutionType: lnrpc.ResolutionType_ANCHOR, Outcome: lnrpc.ResolutionOutcome_CLAIMED, @@ -707,7 +707,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, "limbo") } expectedRecoveredBalance := int64(0) - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { expectedRecoveredBalance = anchorSize } if forceClose.RecoveredBalance != expectedRecoveredBalance { @@ -941,7 +941,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, expectedTxes = numInvoices // In case of anchors, the timeout txs will be aggregated into one. - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { expectedTxes = 1 } @@ -959,7 +959,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // this is an anchor channel, the transactions are aggregated by the // sweeper into one. numInputs := 1 - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { numInputs = numInvoices + 1 } @@ -1087,7 +1087,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // Advance the chain until just before the 2nd-layer CSV delays expire. // For anchor channels thhis is one block earlier. numBlocks := uint32(defaultCSV - 1) - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { numBlocks = defaultCSV - 2 } @@ -1318,7 +1318,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // In addition, if this is an anchor-enabled channel, further add the // anchor size. - if channelType == commitTypeAnchors { + if channelType == lnrpc.CommitmentType_ANCHORS { carolExpectedBalance += btcutil.Amount(anchorSize) } diff --git a/lntest/itest/lnd_funding_test.go b/lntest/itest/lnd_funding_test.go index 492d70e5e75..4b6a941001f 100644 --- a/lntest/itest/lnd_funding_test.go +++ b/lntest/itest/lnd_funding_test.go @@ -14,7 +14,7 @@ import ( "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -31,19 +31,19 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { // Run through the test with combinations of all the different // commitment types. - allTypes := []commitType{ - commitTypeLegacy, - commitTypeTweakless, - commitTypeAnchors, + allTypes := []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_STATIC_REMOTE_KEY, + lnrpc.CommitmentType_ANCHORS, } // testFunding is a function closure that takes Carol and Dave's // commitment types and test the funding flow. - testFunding := func(carolCommitType, daveCommitType commitType) { + testFunding := func(carolCommitType, daveCommitType lnrpc.CommitmentType) { // Based on the current tweak variable for Carol, we'll // preferentially signal the legacy commitment format. We do // the same for Dave shortly below. - carolArgs := carolCommitType.Args() + carolArgs := nodeArgsForCommitType(carolCommitType) carol := net.NewNode(t.t, "Carol", carolArgs) defer shutdownAndAssert(net, t, carol) @@ -52,7 +52,7 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - daveArgs := daveCommitType.Args() + daveArgs := nodeArgsForCommitType(daveCommitType) dave := net.NewNode(t.t, "Dave", daveArgs) defer shutdownAndAssert(net, t, dave) @@ -85,20 +85,20 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { // Dave supports anchors, type will be what // Carol supports. - case commitTypeAnchors: + case lnrpc.CommitmentType_ANCHORS: // Dave only supports tweakless, channel will // be downgraded to this type if Carol supports // anchors. - case commitTypeTweakless: - if expType == commitTypeAnchors { - expType = commitTypeTweakless + case lnrpc.CommitmentType_STATIC_REMOTE_KEY: + if expType == lnrpc.CommitmentType_ANCHORS { + expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY } // Dave only supoprts legacy type, channel will // be downgraded to this type. - case commitTypeLegacy: - expType = commitTypeLegacy + case lnrpc.CommitmentType_LEGACY: + expType = lnrpc.CommitmentType_LEGACY default: t.Fatalf("invalid commit type %v", daveCommitType) @@ -107,13 +107,13 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { // Check that the signalled type matches what we // expect. switch { - case expType == commitTypeAnchors && + case expType == lnrpc.CommitmentType_ANCHORS && chansCommitType == lnrpc.CommitmentType_ANCHORS: - case expType == commitTypeTweakless && + case expType == lnrpc.CommitmentType_STATIC_REMOTE_KEY && chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY: - case expType == commitTypeLegacy && + case expType == lnrpc.CommitmentType_LEGACY && chansCommitType == lnrpc.CommitmentType_LEGACY: default: @@ -226,7 +226,7 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, // With the channel open, ensure that the amount specified above has // properly been pushed to Bob. - aliceLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0) + aliceLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 0) checkChannelBalance( alice, aliceChannelBalance, aliceLocalBalance, pushAmt, ) @@ -351,8 +351,8 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { // // Note that atm we haven't obtained the chanPoint yet, so we use the // type directly. - cType := commitTypeTweakless - carolLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0) + cType := lnrpc.CommitmentType_STATIC_REMOTE_KEY + carolLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 0) checkChannelBalance(carol, 0, 0, carolLocalBalance, pushAmt) // For Alice, her local/remote balances should be zero, and the @@ -407,7 +407,7 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) { const thawHeight uint32 = 10 const chanSize = funding.MaxBtcFundingAmount fundingShim1, chanPoint1, _ := deriveFundingShim( - net, t, carol, dave, chanSize, thawHeight, 1, false, + net, t, carol, dave, chanSize, thawHeight, false, ) _ = openChannelStream( ctxb, t, net, carol, dave, lntest.OpenChannelParams{ @@ -424,7 +424,7 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) { // do exactly that now. For this one we publish the transaction so we // can mine it later. fundingShim2, chanPoint2, _ := deriveFundingShim( - net, t, carol, dave, chanSize, thawHeight, 2, true, + net, t, carol, dave, chanSize, thawHeight, true, ) // At this point, we'll now carry out the normal basic channel funding @@ -667,17 +667,14 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) { // keys on both sides. func deriveFundingShim(net *lntest.NetworkHarness, t *harnessTest, carol, dave *lntest.HarnessNode, chanSize btcutil.Amount, - thawHeight uint32, keyIndex int32, publish bool) (*lnrpc.FundingShim, + thawHeight uint32, publish bool) (*lnrpc.FundingShim, *lnrpc.ChannelPoint, *chainhash.Hash) { ctxb := context.Background() - keyLoc := &signrpc.KeyLocator{ - KeyFamily: 9999, - KeyIndex: keyIndex, - } - carolFundingKey, err := carol.WalletKitClient.DeriveKey(ctxb, keyLoc) + keyLoc := &walletrpc.KeyReq{KeyFamily: 9999} + carolFundingKey, err := carol.WalletKitClient.DeriveNextKey(ctxb, keyLoc) require.NoError(t.t, err) - daveFundingKey, err := dave.WalletKitClient.DeriveKey(ctxb, keyLoc) + daveFundingKey, err := dave.WalletKitClient.DeriveNextKey(ctxb, keyLoc) require.NoError(t.t, err) // Now that we have the multi-sig keys for each party, we can manually diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index aa7bf25dabb..cd67dbe271c 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -1056,7 +1056,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { // on chain, and both of them properly carry out the DLP protocol. assertDLPExecuted( net, t, carol, carolStartingBalance, dave, daveStartingBalance, - false, + lnrpc.CommitmentType_STATIC_REMOTE_KEY, ) // As a second part of this test, we will test the scenario where a diff --git a/lntest/itest/lnd_multi-hop-error-propagation_test.go b/lntest/itest/lnd_multi-hop-error-propagation_test.go index 1bbed544d16..662b18534b2 100644 --- a/lntest/itest/lnd_multi-hop-error-propagation_test.go +++ b/lntest/itest/lnd_multi-hop-error-propagation_test.go @@ -40,7 +40,7 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to get channel type: %v", err) } - commitFee := cType.calcStaticFee(0) + commitFee := calcStaticFee(cType, 0) assertBaseBalance := func() { // Alice has opened a channel with Bob with zero push amount, so // it's remote balance is zero. diff --git a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go index 3c91eed1f78..9045be16ea0 100644 --- a/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go @@ -23,7 +23,7 @@ import ( // case of anchor channels, the second-level spends can also be aggregated and // properly feebumped, so we'll check that as well. func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode, c commitType) { + alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { const finalCltvDelta = 40 ctxb := context.Background() @@ -187,8 +187,9 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, // Bob's force close transaction should now be found in the mempool. If // there are anchors, we also expect Bob's anchor sweep. + hasAnchors := commitTypeHasAnchors(c) expectedTxes := 1 - if c == commitTypeAnchors { + if hasAnchors { expectedTxes = 2 } @@ -251,19 +252,25 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, require.NoError(t.t, err) } + switch c { // With the closing transaction confirmed, we should expect Bob's HTLC // timeout transactions to be broadcast due to the expiry being reached. // We will also expect the success transactions, since he learnt the // preimages from Alice. We also expect Carol to sweep her commitment // output. - expectedTxes = 2*numInvoices + 1 + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2*numInvoices + 1 // In case of anchors, all success transactions will be aggregated into // one, the same is the case for the timeout transactions. In this case - // Carol will also sweep her anchor output in a separate tx (since it - // will be low fee). - if c == commitTypeAnchors { + // Carol will also sweep her commitment and anchor output as separate + // txs (since it will be low fee). + case lnrpc.CommitmentType_ANCHORS, + lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: expectedTxes = 4 + + default: + t.Fatalf("unhandled commitment type %v", c) } txes, err := getNTxsFromMempool( @@ -298,7 +305,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, // In case of anchor we expect all the timeout and success second // levels to be aggregated into one tx. For earlier channel types, they // will be separate transactions. - if c == commitTypeAnchors { + if hasAnchors { require.Len(t.t, timeoutTxs, 1) require.Len(t.t, successTxs, 1) } else { @@ -341,47 +348,75 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, ) require.NoError(t.t, err) - // If we then mine additional blocks, Bob can sweep his commitment - // output. - _, err = net.Miner.Client.Generate(defaultCSV - 2) - require.NoError(t.t, err) + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // If we then mine additional blocks, Bob can sweep his commitment + // output. + _, err = net.Miner.Client.Generate(defaultCSV - 2) + require.NoError(t.t, err) - // Find the commitment sweep. - bobCommitSweepHash, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) - bobCommitSweep, err := net.Miner.Client.GetRawTransaction(bobCommitSweepHash) - require.NoError(t.t, err) + // Find the commitment sweep. + bobCommitSweepHash, err := waitForTxInMempool( + net.Miner.Client, minerMempoolTimeout, + ) + require.NoError(t.t, err) + bobCommitSweep, err := net.Miner.Client.GetRawTransaction( + bobCommitSweepHash, + ) + require.NoError(t.t, err) - require.Equal( - t.t, closeTxid, bobCommitSweep.MsgTx().TxIn[0].PreviousOutPoint.Hash, - ) + require.Equal( + t.t, closeTxid, + bobCommitSweep.MsgTx().TxIn[0].PreviousOutPoint.Hash, + ) - // Also ensure it is not spending from any of the HTLC output. - for _, txin := range bobCommitSweep.MsgTx().TxIn { - for _, timeoutTx := range timeoutTxs { - if *timeoutTx == txin.PreviousOutPoint.Hash { - t.Fatalf("found unexpected spend of timeout tx") + // Also ensure it is not spending from any of the HTLC output. + for _, txin := range bobCommitSweep.MsgTx().TxIn { + for _, timeoutTx := range timeoutTxs { + if *timeoutTx == txin.PreviousOutPoint.Hash { + t.Fatalf("found unexpected spend of " + + "timeout tx") + } } - } - for _, successTx := range successTxs { - if *successTx == txin.PreviousOutPoint.Hash { - t.Fatalf("found unexpected spend of success tx") + for _, successTx := range successTxs { + if *successTx == txin.PreviousOutPoint.Hash { + t.Fatalf("found unexpected spend of " + + "success tx") + } } } } - switch { + switch c { + // In case this is a non-anchor channel type, we must mine 2 blocks, as + // the nursery waits an extra block before sweeping. Before the blocks + // are mined, we should expect to see Bob's commit sweep in the mempool. + case lnrpc.CommitmentType_LEGACY: + _ = mineBlocks(t, net, 2, 1) // Mining one additional block, Bob's second level tx is mature, and he - // can sweep the output. - case c == commitTypeAnchors: + // can sweep the output. Before the blocks are mined, we should expect + // to see Bob's commit sweep in the mempool. + case lnrpc.CommitmentType_ANCHORS: _ = mineBlocks(t, net, 1, 1) - // In case this is a non-anchor channel type, we must mine 2 blocks, as - // the nursery waits an extra block before sweeping. + // Since Bob is the initiator of the Bob-Carol script-enforced leased + // channel, he incurs an additional CLTV when sweeping outputs back to + // his wallet. We'll need to mine enough blocks for the timelock to + // expire to prompt his broadcast. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + resp, err := bob.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + require.Len(t.t, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + _ = mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity), 0) + default: - _ = mineBlocks(t, net, 2, 1) + t.Fatalf("unhandled commitment type %v", c) } bobSweep, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) diff --git a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go index 0e37cd3a370..18c9eea4850 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go @@ -20,7 +20,7 @@ import ( // preimage via the witness beacon, we properly settle the HTLC on-chain using // the HTLC success transaction in order to ensure we don't lose any funds. func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode, c commitType) { + alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -86,15 +86,29 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, // At this point, Bob decides that he wants to exit the channel // immediately, so he force closes his commitment transaction. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + hasAnchors := commitTypeHasAnchors(c) bobForceClose := closeChannelAndAssertType( - ctxt, t, net, bob, aliceChanPoint, c == commitTypeAnchors, true, + ctxt, t, net, bob, aliceChanPoint, hasAnchors, true, ) - // Alice will sweep her commitment output immediately. If there are - // anchors, Alice will also sweep hers. - expectedTxes := 1 - if c == commitTypeAnchors { + var expectedTxes int + switch c { + // Alice will sweep her commitment output immediately. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 1 + + // Alice will sweep her commitment and anchor output immediately. + case lnrpc.CommitmentType_ANCHORS: expectedTxes = 2 + + // Alice will sweep her anchor output immediately. Her commitment output + // cannot be swept yet as it has incurred an additional CLTV due to + // being the initiator of a script-enforced leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 1 + + default: + t.Fatalf("unhandled commitment type %v", c) } _, err = waitForNTxsInMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, @@ -155,15 +169,28 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, err = restartBob() require.NoError(t.t, err) - // After the force close transacion is mined, Carol should broadcast her - // second level HTLC transacion. Bob will broadcast a sweep tx to sweep - // his output in the channel with Carol. He can do this immediately, as - // the output is not timelocked since Carol was the one force closing. - // If there are anchors on the commitment, Bob will also sweep his - // anchor. - expectedTxes = 2 - if c == commitTypeAnchors { + // After the force close transacion is mined, transactions will be + // broadcast by both Bob and Carol. + switch c { + // Carol will broadcast her second level HTLC transaction and Bob will + // sweep his commitment output. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol will broadcast her second level HTLC transaction and Bob will + // sweep his commitment and anchor output. + case lnrpc.CommitmentType_ANCHORS: + expectedTxes = 3 + + // Carol will broadcast her second level HTLC transaction and anchor + // sweep, and Bob will sweep his anchor output. Bob can't sweep his + // commitment output yet as it has incurred an additional CLTV due to + // being the initiator of a script-enforced leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: expectedTxes = 3 + + default: + t.Fatalf("unhandled commitment type %v", c) } txes, err := getNTxsFromMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, @@ -179,24 +206,20 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, restartAlice, err := net.SuspendNode(alice) require.NoError(t.t, err) - // Mine a block to confirm the two transactions (+ the coinbase). + // Mine a block to confirm the expected transactions (+ the coinbase). block = mineBlocks(t, net, 1, expectedTxes)[0] require.Len(t.t, block.Transactions, expectedTxes+1) - var secondLevelMaturity uint32 - switch c { - - // If this is a channel of the anchor type, we will subtract one block - // from the default CSV, as the Sweeper will handle the input, and the Sweeper - // sweeps the input as soon as the lock expires. - case commitTypeAnchors: - secondLevelMaturity = defaultCSV - 1 - // For non-anchor channel types, the nursery will handle sweeping the // second level output, and it will wait one extra block before // sweeping it. - default: - secondLevelMaturity = defaultCSV + secondLevelMaturity := uint32(defaultCSV) + + // If this is a channel of the anchor type, we will subtract one block + // from the default CSV, as the Sweeper will handle the input, and the + // Sweeper sweeps the input as soon as the lock expires. + if hasAnchors { + secondLevelMaturity = defaultCSV - 1 } // Keep track of the second level tx maturity. @@ -220,9 +243,13 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, // At this point, Bob should have broadcast his second layer success // transaction, and should have sent it to the nursery for incubation. + numPendingChans := 1 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + numPendingChans++ + } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = waitForNumChannelPendingForceClose( - ctxt, bob, 1, func(c *lnrpcForceCloseChannel) error { + ctxt, bob, numPendingChans, func(c *lnrpcForceCloseChannel) error { if c.Channel.LocalBalance != 0 { return nil } @@ -289,15 +316,59 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, block = mineBlocks(t, net, 1, 1)[0] assertTxInBlock(t, block, bobSweep) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = waitForNumChannelPendingForceClose(ctxt, bob, 0, nil) - require.NoError(t.t, err) - assertNodeNumChannels(t, bob, 0) + // With the script-enforced lease commitment type, Alice and Bob still + // haven't been able to sweep their respective commit outputs due to the + // additional CLTV. We'll need to mine enough blocks for the timelock to + // expire and prompt their sweep. + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + for _, node := range []*lntest.HarnessNode{alice, bob} { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForNumChannelPendingForceClose(ctxt, node, 1, nil) + require.NoError(t.t, err) + } + + // Due to the way the test is set up, Alice and Bob share the + // same CLTV for their commit outputs even though it's enforced + // on different channels (Alice-Bob and Bob-Carol). + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := alice.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + require.Len(t.t, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + + // Mine enough blocks for the timelock to expire. + numBlocks := uint32(forceCloseChan.BlocksTilMaturity) + _, err = net.Miner.Client.Generate(numBlocks) + require.NoError(t.t, err) + + // Both Alice and Bob show broadcast their commit sweeps. + aliceCommitOutpoint := wire.OutPoint{Hash: *bobForceClose, Index: 3} + aliceCommitSweep := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + aliceCommitOutpoint, + ) + bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + bobCommitSweep := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + bobCommitOutpoint, + ) + + // Confirm their sweeps. + block := mineBlocks(t, net, 1, 2)[0] + assertTxInBlock(t, block, &aliceCommitSweep) + assertTxInBlock(t, block, &bobCommitSweep) + } - // Also Carol should have no channels left (open nor pending). - err = waitForNumChannelPendingForceClose(ctxt, carol, 0, nil) - require.NoError(t.t, err) - assertNodeNumChannels(t, carol, 0) + // All nodes should show zero pending and open channels. + for _, node := range []*lntest.HarnessNode{alice, bob, carol} { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForNumChannelPendingForceClose(ctxt, node, 0, nil) + require.NoError(t.t, err) + assertNodeNumChannels(t, node, 0) + } // Finally, check that the Alice's payment is correctly marked // succeeded. diff --git a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go index e110f7defb2..6bbcda93b65 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go @@ -4,7 +4,6 @@ import ( "context" "time" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lncfg" @@ -21,7 +20,7 @@ import ( // canceled backwards. Once the timeout has been reached, then we should sweep // it on-chain, and cancel the HTLC backwards. func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode, c commitType) { + alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -104,23 +103,24 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, // Bob's force close transaction should now be found in the mempool. If // there are anchors, we also expect Bob's anchor sweep. expectedTxes := 1 - if c == commitTypeAnchors { + hasAnchors := commitTypeHasAnchors(c) + if hasAnchors { expectedTxes = 2 } - - bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) - require.NoError(t.t, err) _, err = waitForNTxsInMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, ) require.NoError(t.t, err) - closeTx := getSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{ - Hash: *bobFundingTxid, - Index: bobChanPoint.OutputIndex, - }, + + bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) + require.NoError(t.t, err) + bobChanOutpoint := wire.OutPoint{ + Hash: *bobFundingTxid, + Index: bobChanPoint.OutputIndex, + } + closeTxid := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, bobChanOutpoint, ) - closeTxid := closeTx.TxHash() // Mine a block to confirm the closing transaction. mineBlocks(t, net, 1, expectedTxes) @@ -137,87 +137,86 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, // With the closing transaction confirmed, we should expect Bob's HTLC // timeout transaction to be broadcast due to the expiry being reached. // If there are anchors, we also expect Carol's anchor sweep now. - txes, err := getNTxsFromMempool( + _, err = getNTxsFromMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, ) require.NoError(t.t, err) - // Lookup the timeout transaction that is expected to spend from the - // closing tx. We distinguish it from a possibly anchor sweep by value. - var htlcTimeout *chainhash.Hash - for _, tx := range txes { - prevOp := tx.TxIn[0].PreviousOutPoint - require.Equal(t.t, closeTxid, prevOp.Hash) - - // Assume that the timeout tx doesn't spend an output of exactly - // the size of the anchor. - if closeTx.TxOut[prevOp.Index].Value != anchorSize { - hash := tx.TxHash() - htlcTimeout = &hash - } + // We'll also obtain the expected HTLC timeout transaction hash. + htlcOutpoint := wire.OutPoint{Hash: closeTxid, Index: 0} + commitOutpoint := wire.OutPoint{Hash: closeTxid, Index: 1} + if hasAnchors { + htlcOutpoint.Index = 2 + commitOutpoint.Index = 3 } - require.NotNil(t.t, htlcTimeout) + htlcTimeoutTxid := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint, + ) - // We'll mine the remaining blocks in order to generate the sweep - // transaction of Bob's commitment output. The commitment was just - // mined at the current tip and the sweep will be broadcast so it can - // be mined at the tip+defaultCSV'th block, so mine one less to be able - // to make mempool assertions. - mineBlocks(t, net, defaultCSV-1, expectedTxes) + // Mine a block to confirm the expected transactions. + _ = mineBlocks(t, net, 1, expectedTxes) - // Check that the sweep spends from the mined commitment. - txes, err = getNTxsFromMempool(net.Miner.Client, 1, minerMempoolTimeout) + // With Bob's HTLC timeout transaction confirmed, there should be no + // active HTLC's on the commitment transaction from Alice -> Bob. + err = wait.NoError(func() error { + return assertNumActiveHtlcs([]*lntest.HarnessNode{alice}, 0) + }, defaultTimeout) require.NoError(t.t, err) - assertAllTxesSpendFrom(t, txes, closeTxid) - // Bob's pending channel report should show that he has a commitment - // output awaiting sweeping, and also that there's an outgoing HTLC - // output pending. - pendingChansRequest := &lnrpc.PendingChannelsRequest{} + // At this point, Bob should show that the pending HTLC has advanced to + // the second stage and is ready to be swept once the timelock is up. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + pendingChansRequest := &lnrpc.PendingChannelsRequest{} pendingChanResp, err := bob.PendingChannels(ctxt, pendingChansRequest) require.NoError(t.t, err) - - require.NotZero(t.t, len(pendingChanResp.PendingForceClosingChannels)) + require.Equal(t.t, 1, len(pendingChanResp.PendingForceClosingChannels)) forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] require.NotZero(t.t, forceCloseChan.LimboBalance) - require.NotZero(t.t, len(forceCloseChan.PendingHtlcs)) - - // Mine a block to confirm Bob's commit sweep tx and assert it was in - // fact mined. - block := mineBlocks(t, net, 1, 1)[0] - commitSweepTxid := txes[0].TxHash() - assertTxInBlock(t, block, &commitSweepTxid) - - // Mine an additional block to prompt Bob to broadcast their second - // layer sweep due to the CSV on the HTLC timeout output. - mineBlocks(t, net, 1, 0) - assertSpendingTxInMempool( - t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{ - Hash: *htlcTimeout, - Index: 0, - }, - ) - - // The block should have confirmed Bob's HTLC timeout transaction. - // Therefore, at this point, there should be no active HTLC's on the - // commitment transaction from Alice -> Bob. - nodes = []*lntest.HarnessNode{alice} - err = wait.NoError(func() error { - return assertNumActiveHtlcs(nodes, 0) - }, defaultTimeout) - require.NoError(t.t, err) - - // At this point, Bob should show that the pending HTLC has advanced to - // the second stage and is to be swept. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - pendingChanResp, err = bob.PendingChannels(ctxt, pendingChansRequest) - require.NoError(t.t, err) - forceCloseChan = pendingChanResp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + require.Equal(t.t, 1, len(forceCloseChan.PendingHtlcs)) require.Equal(t.t, uint32(2), forceCloseChan.PendingHtlcs[0].Stage) - // Next, we'll mine a final block that should confirm the second-layer - // sweeping transaction. + htlcTimeoutOutpoint := wire.OutPoint{Hash: htlcTimeoutTxid, Index: 0} + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Since Bob is the initiator of the script-enforced leased + // channel between him and Carol, he will incur an additional + // CLTV on top of the usual CSV delay on any outputs that he can + // sweep back to his wallet. + blocksTilMaturity := uint32(forceCloseChan.BlocksTilMaturity) + mineBlocks(t, net, blocksTilMaturity, 0) + + // Check that the sweep spends the expected inputs. + _ = assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + commitOutpoint, htlcTimeoutOutpoint, + ) + } else { + // Since Bob force closed the channel between him and Carol, he + // will incur the usual CSV delay on any outputs that he can + // sweep back to his wallet. We'll subtract one block from our + // current maturity period to assert on the mempool. + mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity-1), 0) + + // Check that the sweep spends from the mined commitment. + _ = assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, commitOutpoint, + ) + + // Mine a block to confirm Bob's commit sweep tx and assert it + // was in fact mined. + _ = mineBlocks(t, net, 1, 1)[0] + + // Mine an additional block to prompt Bob to broadcast their + // second layer sweep due to the CSV on the HTLC timeout output. + mineBlocks(t, net, 1, 0) + _ = assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + htlcTimeoutOutpoint, + ) + } + + // Next, we'll mine a final block that should confirm the sweeping + // transactions left. _, err = net.Miner.Client.Generate(1) require.NoError(t.t, err) diff --git a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go index eb55344cfa2..11682d8e74d 100644 --- a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go @@ -22,7 +22,7 @@ import ( // extract the preimage from the sweep transaction, and finish settling the // HTLC backwards into the route. func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode, c commitType) { + alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -113,7 +113,8 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, // transaction in order to go to the chain and sweep her HTLC. If there // are anchors, Carol also sweeps hers. expectedTxes := 1 - if c == commitTypeAnchors { + hasAnchors := commitTypeHasAnchors(c) + if hasAnchors { expectedTxes = 2 } _, err = getNTxsFromMempool( @@ -143,15 +144,32 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, err = restartBob() require.NoError(t.t, err) - // After the force close transaction is mined, Carol should broadcast - // her second level HTLC transaction. Bob will broadcast a sweep tx to - // sweep his output in the channel with Carol. When Bob notices Carol's - // second level transaction in the mempool, he will extract the preimage - // and settle the HTLC back off-chain. Bob will also sweep his anchor, - // if present. - expectedTxes = 2 - if c == commitTypeAnchors { + // After the force close transaction is mined, a series of transactions + // should be broadcast by Bob and Carol. When Bob notices Carol's second + // level transaction in the mempool, he will extract the preimage and + // settle the HTLC back off-chain. + switch c { + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his output in the channel with + // Carol. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his output in the channel with + // Carol, and another sweep tx to sweep his anchor output. + case lnrpc.CommitmentType_ANCHORS: expectedTxes = 3 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a sweep tx to sweep his anchor output. Bob's commit + // output can't be swept yet as he's incurring an additional CLTV from + // being the channel initiator of a script-enforced leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 2 + + default: + t.Fatalf("unhandled commitment type %v", c) } txes, err := getNTxsFromMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, @@ -195,8 +213,8 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, }, defaultTimeout) require.NoError(t.t, err) - // If we mine 4 additional blocks, then both outputs should now be - // mature. + // If we mine 4 additional blocks, then Carol can sweep the second level + // HTLC output. _, err = net.Miner.Client.Generate(defaultCSV) require.NoError(t.t, err) @@ -231,6 +249,42 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, ) require.NoError(t.t, err) + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Bob still has his commit output to sweep to since he incurred + // an additional CLTV from being the channel initiator of a + // script-enforced leased channel, regardless of whether he + // forced closed the channel or not. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + pendingChanResp, err := bob.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + + require.Len(t.t, pendingChanResp.PendingForceClosingChannels, 1) + forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.LimboBalance) + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + + // TODO: Bob still shows a pending HTLC at this point when he + // shouldn't, as he already extracted the preimage from Carol's + // claim. + // require.Len(t.t, forceCloseChan.PendingHtlcs, 0) + + // Mine enough blocks for Bob's commit output's CLTV to expire + // and sweep it. + _ = mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity), 0) + commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, commitOutpoint, + ) + _, err = net.Miner.Client.Generate(1) + require.NoError(t.t, err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForNumChannelPendingForceClose(ctxt, bob, 0, nil) + require.NoError(t.t, err) + // We'll close out the channel between Alice and Bob, then shutdown // carol to conclude the test. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) diff --git a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go index 842c5c2981a..d0a52d7a774 100644 --- a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go @@ -20,7 +20,7 @@ import ( // HTLC directly on-chain using the preimage in order to ensure that we don't // lose any funds. func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest, - alice, bob *lntest.HarnessNode, c commitType) { + alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -86,9 +86,9 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest // immediately force close the channel by broadcast her commitment // transaction. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + hasAnchors := commitTypeHasAnchors(c) aliceForceClose := closeChannelAndAssertType( - ctxt, t, net, alice, aliceChanPoint, c == commitTypeAnchors, - true, + ctxt, t, net, alice, aliceChanPoint, hasAnchors, true, ) // Wait for the channel to be marked pending force close. @@ -99,29 +99,29 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest // After closeChannelAndAssertType returns, it has mined a block so now // bob will attempt to redeem his anchor commitment (if the channel // type is of that type). - if c == commitTypeAnchors { + if hasAnchors { _, err = waitForNTxsInMempool( net.Miner.Client, 1, minerMempoolTimeout, ) - if err != nil { - t.Fatalf("unable to find bob's anchor commit sweep: %v", - err) - } + require.NoError(t.t, err) } - // Mine enough blocks for Alice to sweep her funds from the force - // closed channel. closeChannelAndAssertType() already mined a block - // containing the commitment tx and the commit sweep tx will be - // broadcast immediately before it can be included in a block, so mine - // one less than defaultCSV in order to perform mempool assertions. - _, err = net.Miner.Client.Generate(defaultCSV - 1) - require.NoError(t.t, err) - - // Alice should now sweep her funds. - _, err = waitForNTxsInMempool( - net.Miner.Client, 1, minerMempoolTimeout, - ) - require.NoError(t.t, err) + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Mine enough blocks for Alice to sweep her funds from the + // force closed channel. closeChannelAndAssertType() already + // mined a block containing the commitment tx and the commit + // sweep tx will be broadcast immediately before it can be + // included in a block, so mine one less than defaultCSV in + // order to perform mempool assertions. + _, err = net.Miner.Client.Generate(defaultCSV - 1) + require.NoError(t.t, err) + + // Alice should now sweep her funds. + _, err = waitForNTxsInMempool( + net.Miner.Client, 1, minerMempoolTimeout, + ) + require.NoError(t.t, err) + } // Suspend bob, so Carol is forced to go on chain. restartBob, err := net.SuspendNode(bob) @@ -142,14 +142,17 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest // We'll now mine enough blocks so Carol decides that she needs to go // on-chain to claim the HTLC as Bob has been inactive. numBlocks := padCLTV(uint32( - invoiceReq.CltvExpiry-lncfg.DefaultIncomingBroadcastDelta, - ) - defaultCSV) + invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta, + )) + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + numBlocks -= defaultCSV + } _, err = net.Miner.Client.Generate(numBlocks) require.NoError(t.t, err) expectedTxes := 1 - if c == commitTypeAnchors { + if hasAnchors { expectedTxes = 2 } @@ -183,14 +186,31 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest err = restartBob() require.NoError(t.t, err) - // After the force close transacion is mined, Carol should broadcast her - // second level HTLC transacion. Bob will broadcast a sweep tx to sweep - // his output in the channel with Carol. He can do this immediately, as - // the output is not timelocked since Carol was the one force closing. - // If there are anchors, Bob should also sweep his. - expectedTxes = 2 - if c == commitTypeAnchors { + // After the force close transacion is mined, we should expect Bob and + // Carol to broadcast some transactions depending on the channel + // commitment type. + switch c { + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his commitment output. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 2 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his commitment output and + // another to sweep his anchor output. + case lnrpc.CommitmentType_ANCHORS: expectedTxes = 3 + + // Carol should broadcast her second level HTLC transaction and Bob + // should broadcast a transaction to sweep his anchor output. Bob can't + // sweep his commitment output yet as he has incurred an additional CLTV + // due to being the channel initiator of a force closed script-enforced + // leased channel. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 2 + + default: + t.Fatalf("unhandled commitment type %v", c) } txes, err := getNTxsFromMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, @@ -230,11 +250,19 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest carolSecondLevelCSV-- // Now that the sweeping transaction has been confirmed, Bob should now - // recognize that all contracts have been fully resolved, and show no - // pending close channels. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = waitForNumChannelPendingForceClose(ctxt, bob, 0, nil) - require.NoError(t.t, err) + // recognize that all contracts for the Bob-Carol channel have been + // fully resolved + aliceBobPendingChansLeft := 0 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + aliceBobPendingChansLeft = 1 + } + for _, node := range []*lntest.HarnessNode{alice, bob} { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForNumChannelPendingForceClose( + ctxt, node, aliceBobPendingChansLeft, nil, + ) + require.NoError(t.t, err) + } // If we then mine 3 additional blocks, Carol's second level tx will // mature, and she should pull the funds. @@ -255,6 +283,54 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest err = waitForNumChannelPendingForceClose(ctxt, carol, 0, nil) require.NoError(t.t, err) + // With the script-enforced lease commitment type, Alice and Bob still + // haven't been able to sweep their respective commit outputs due to the + // additional CLTV. We'll need to mine enough blocks for the timelock to + // expire and prompt their sweep. + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Due to the way the test is set up, Alice and Bob share the + // same CLTV for their commit outputs even though it's enforced + // on different channels (Alice-Bob and Bob-Carol). + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := alice.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + require.Len(t.t, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + + // Mine enough blocks for the timelock to expire. + numBlocks := uint32(forceCloseChan.BlocksTilMaturity) + _, err = net.Miner.Client.Generate(numBlocks) + require.NoError(t.t, err) + + // Both Alice and Bob show broadcast their commit sweeps. + aliceCommitOutpoint := wire.OutPoint{Hash: *aliceForceClose, Index: 3} + aliceCommitSweep := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + aliceCommitOutpoint, + ) + bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} + bobCommitSweep := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + bobCommitOutpoint, + ) + + // Confirm their sweeps. + block := mineBlocks(t, net, 1, 2)[0] + assertTxInBlock(t, block, &aliceCommitSweep) + assertTxInBlock(t, block, &bobCommitSweep) + + // Alice and Bob should not show any pending channels anymore as + // they have been fully resolved. + for _, node := range []*lntest.HarnessNode{alice, bob} { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = waitForNumChannelPendingForceClose(ctxt, node, 0, nil) + require.NoError(t.t, err) + } + } + // The invoice should show as settled for Carol, indicating that it was // swept on-chain. invoicesReq := &lnrpc.ListInvoiceRequest{} diff --git a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go index 0d148652c4b..39dd7c57ebd 100644 --- a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go @@ -4,7 +4,9 @@ import ( "context" "fmt" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" @@ -17,7 +19,7 @@ import ( // that's timed out. At this point, the node should timeout the HTLC using the // HTLC timeout transaction, then cancel it backwards as normal. func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, - t *harnessTest, alice, bob *lntest.HarnessNode, c commitType) { + t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -72,8 +74,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // force close the Bob -> Carol channel. This should trigger contract // resolution mode for both of them. ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssertType( - ctxt, t, net, bob, bobChanPoint, c == commitTypeAnchors, true, + hasAnchors := commitTypeHasAnchors(c) + closeTx := closeChannelAndAssertType( + ctxt, t, net, bob, bobChanPoint, hasAnchors, true, ) // At this point, Bob should have a pending force close channel as he @@ -92,27 +95,45 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, ) require.NoError(t.t, err) - // We'll mine defaultCSV blocks in order to generate the sweep - // transaction of Bob's funding output. If there are anchors, mine - // Carol's anchor sweep too. - if c == commitTypeAnchors { + // If the channel closed has anchors, we should expect to see a sweep + // transaction for Carol's anchor. + htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0} + bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 1} + if hasAnchors { + htlcOutpoint.Index = 2 + bobCommitOutpoint.Index = 3 _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) require.NoError(t.t, err) } - // The sweep is broadcast on the block immediately before the CSV - // expires and the commitment was already mined inside - // closeChannelAndAssertType(), so mine one block less than defaultCSV - // in order to perform mempool assertions. - _, err = net.Miner.Client.Generate(defaultCSV - 1) - require.NoError(t.t, err) + // Before the HTLC times out, we'll need to assert that Bob broadcasts a + // sweep transaction for his commit output. Note that if the channel has + // a script-enforced lease, then Bob will have to wait for an additional + // CLTV before sweeping it. + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // The sweep is broadcast on the block immediately before the + // CSV expires and the commitment was already mined inside + // closeChannelAndAssertType(), so mine one block less than + // defaultCSV in order to perform mempool assertions. + _, err = net.Miner.Client.Generate(defaultCSV - 1) + require.NoError(t.t, err) - _, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) + commitSweepTx := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + bobCommitOutpoint, + ) + blocks := mineBlocks(t, net, 1, 1) + assertTxInBlock(t, blocks[0], &commitSweepTx) + } // We'll now mine enough blocks for the HTLC to expire. After this, Bob // should hand off the now expired HTLC output to the utxo nursery. - numBlocks := padCLTV(uint32(finalCltvDelta - defaultCSV)) + numBlocks := padCLTV(finalCltvDelta) + if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + // Subtract the number of blocks already mined to confirm Bob's + // commit sweep. + numBlocks -= defaultCSV + } _, err = net.Miner.Client.Generate(numBlocks) require.NoError(t.t, err) @@ -138,13 +159,14 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // We should also now find a transaction in the mempool, as Bob should // have broadcast his second layer timeout transaction. - timeoutTx, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) - require.NoError(t.t, err) + timeoutTx := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint, + ) // Next, we'll mine an additional block. This should serve to confirm // the second layer timeout transaction. block := mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, timeoutTx) + assertTxInBlock(t, block, &timeoutTx) // With the second layer timeout transaction confirmed, Bob should have // canceled backwards the HTLC that carol sent. @@ -174,19 +196,37 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, ) require.NoError(t.t, err) - // We'll now mine 4 additional blocks. This should be enough for Bob's - // CSV timelock to expire and the sweeping transaction of the HTLC to be - // broadcast. - _, err = net.Miner.Client.Generate(defaultCSV) + // Bob should now broadcast a transaction that sweeps certain inputs + // depending on the commitment type. We'll need to mine some blocks + // before the broadcast is possible. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := bob.PendingChannels(ctxt, &lnrpc.PendingChannelsRequest{}) require.NoError(t.t, err) - sweepTx, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout) + require.Len(t.t, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Len(t.t, forceCloseChan.PendingHtlcs, 1) + pendingHtlc := forceCloseChan.PendingHtlcs[0] + require.Positive(t.t, pendingHtlc.BlocksTilMaturity) + numBlocks = uint32(pendingHtlc.BlocksTilMaturity) + + _, err = net.Miner.Client.Generate(numBlocks) require.NoError(t.t, err) - // We'll then mine a final block which should confirm this second layer - // sweep transaction. + // Now that the CSV/CLTV timelock has expired, the transaction should + // either only sweep the HTLC timeout transaction, or sweep both the + // HTLC timeout transaction and Bob's commit output depending on the + // commitment type. + htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0} + expectedInputsSwept := []wire.OutPoint{htlcTimeoutOutpoint} + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + expectedInputsSwept = append(expectedInputsSwept, bobCommitOutpoint) + } + sweepTx := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, expectedInputsSwept..., + ) block = mineBlocks(t, net, 1, 1)[0] - assertTxInBlock(t, block, sweepTx) + assertTxInBlock(t, block, &sweepTx) // At this point, Bob should no longer show any channels as pending // close. diff --git a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go index cf237c89263..961b3165e6a 100644 --- a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -20,7 +21,7 @@ import ( // transaction once the timeout has expired. Once we sweep the transaction, we // should also cancel back the initial HTLC. func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, - t *harnessTest, alice, bob *lntest.HarnessNode, c commitType) { + t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { ctxb := context.Background() @@ -84,9 +85,9 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // expired HTLC on Carol's version of the commitment transaction. If // Carol has an anchor, it will be swept too. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssertType( - ctxt, t, net, carol, bobChanPoint, c == commitTypeAnchors, - true, + hasAnchors := commitTypeHasAnchors(c) + closeTx := closeChannelAndAssertType( + ctxt, t, net, carol, bobChanPoint, hasAnchors, true, ) // At this point, Bob should have a pending force close channel as @@ -95,13 +96,25 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, err = waitForNumChannelPendingForceClose(ctxt, bob, 1, nil) require.NoError(t.t, err) - // Bob can sweep his output immediately. If there is an anchor, Bob will - // sweep that as well. - expectedTxes := 1 - if c == commitTypeAnchors { + var expectedTxes int + switch c { + // Bob can sweep his commit output immediately. + case lnrpc.CommitmentType_LEGACY: + expectedTxes = 1 + + // Bob can sweep his commit and anchor outputs immediately. + case lnrpc.CommitmentType_ANCHORS: expectedTxes = 2 - } + // Bob can't sweep his commit output yet as he was the initiator of a + // script-enforced leased channel, so he'll always incur the additional + // CLTV. He can still sweep his anchor output however. + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + expectedTxes = 1 + + default: + t.Fatalf("unhandled commitment type %v", c) + } _, err = waitForNTxsInMempool( net.Miner.Client, expectedTxes, minerMempoolTimeout, ) @@ -172,8 +185,32 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, require.NoError(t.t, err) // Now we'll check Bob's pending channel report. Since this was Carol's - // commitment, he doesn't have to wait for any CSV delays. As a result, - // he should show no additional pending transactions. + // commitment, he doesn't have to wait for any CSV delays, but he may + // still need to wait for a CLTV on his commit output to expire + // depending on the commitment type. + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := bob.PendingChannels( + ctxt, &lnrpc.PendingChannelsRequest{}, + ) + require.NoError(t.t, err) + + require.Len(t.t, resp.PendingForceClosingChannels, 1) + forceCloseChan := resp.PendingForceClosingChannels[0] + require.Positive(t.t, forceCloseChan.BlocksTilMaturity) + + numBlocks := uint32(forceCloseChan.BlocksTilMaturity) + _, err = net.Miner.Client.Generate(numBlocks) + require.NoError(t.t, err) + + bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3} + bobCommitSweep := assertSpendingTxInMempool( + t, net.Miner.Client, minerMempoolTimeout, + bobCommitOutpoint, + ) + block := mineBlocks(t, net, 1, 1)[0] + assertTxInBlock(t, block, &bobCommitSweep) + } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = waitForNumChannelPendingForceClose(ctxt, bob, 0, nil) require.NoError(t.t, err) diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index 536cc63100b..f8af2e6de9e 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -12,6 +12,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntypes" + "github.com/stretchr/testify/require" ) func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { @@ -19,7 +20,7 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { type testCase struct { name string test func(net *lntest.NetworkHarness, t *harnessTest, alice, - bob *lntest.HarnessNode, c commitType) + bob *lntest.HarnessNode, c lnrpc.CommitmentType) } subTests := []testCase{ @@ -68,19 +69,20 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { }, } - commitTypes := []commitType{ - commitTypeLegacy, - commitTypeAnchors, + commitTypes := []lnrpc.CommitmentType{ + lnrpc.CommitmentType_LEGACY, + lnrpc.CommitmentType_ANCHORS, + lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, } for _, commitType := range commitTypes { + commitType := commitType testName := fmt.Sprintf("committype=%v", commitType.String()) - commitType := commitType success := t.t.Run(testName, func(t *testing.T) { ht := newHarnessTest(t, net) - args := commitType.Args() + args := nodeArgsForCommitType(commitType) alice := net.NewNode(t, "Alice", args) defer shutdownAndAssert(net, ht, alice) @@ -203,7 +205,7 @@ func checkPaymentStatus(ctxt context.Context, node *lntest.HarnessNode, } func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, - alice, bob *lntest.HarnessNode, carolHodl bool, c commitType) ( + alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) ( *lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) { ctxb := context.Background() @@ -223,11 +225,23 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, // We'll start the test by creating a channel between Alice and Bob, // which will act as the first leg for out multi-hop HTLC. const chanAmt = 1000000 + var aliceFundingShim *lnrpc.FundingShim + var thawHeight uint32 + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + _, minerHeight, err := net.Miner.Client.GetBestBlock() + require.NoError(t.t, err) + thawHeight = uint32(minerHeight + 144) + aliceFundingShim, _, _ = deriveFundingShim( + net, t, alice, bob, chanAmt, thawHeight, true, + ) + } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) aliceChanPoint := openChannelAndAssert( ctxt, t, net, alice, bob, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + CommitmentType: c, + FundingShim: aliceFundingShim, }, ) @@ -246,7 +260,7 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, // Next, we'll create a new node "carol" and have Bob connect to her. If // the carolHodl flag is set, we'll make carol always hold onto the // HTLC, this way it'll force Bob to go to chain to resolve the HTLC. - carolFlags := c.Args() + carolFlags := nodeArgsForCommitType(c) if carolHodl { carolFlags = append(carolFlags, "--hodl.exit-settle") } @@ -266,11 +280,19 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, // We'll then create a channel from Bob to Carol. After this channel is // open, our topology looks like: A -> B -> C. + var bobFundingShim *lnrpc.FundingShim + if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { + bobFundingShim, _, _ = deriveFundingShim( + net, t, bob, carol, chanAmt, thawHeight, true, + ) + } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) bobChanPoint := openChannelAndAssert( ctxt, t, net, bob, carol, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + CommitmentType: c, + FundingShim: bobFundingShim, }, ) ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) diff --git a/lntest/itest/lnd_onchain_test.go b/lntest/itest/lnd_onchain_test.go index f3e278e1707..f79d0a9c14a 100644 --- a/lntest/itest/lnd_onchain_test.go +++ b/lntest/itest/lnd_onchain_test.go @@ -168,7 +168,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { // wallet. func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) { // Start two nodes supporting anchor channels. - args := commitTypeAnchors.Args() + args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS) alice := net.NewNode(t.t, "Alice", args) defer shutdownAndAssert(net, t, alice) diff --git a/lntest/itest/utils.go b/lntest/itest/utils.go index 1d354a126af..b4beecc1c2e 100644 --- a/lntest/itest/utils.go +++ b/lntest/itest/utils.go @@ -203,46 +203,32 @@ func getChanInfo(ctx context.Context, node *lntest.HarnessNode) ( return channelInfo.Channels[0], nil } -// commitType is a simple enum used to run though the basic funding flow with -// different commitment formats. -type commitType byte - -const ( - // commitTypeLegacy is the old school commitment type. - commitTypeLegacy commitType = iota - - // commiTypeTweakless is the commitment type where the remote key is - // static (non-tweaked). - commitTypeTweakless - - // commitTypeAnchors is the kind of commitment that has extra outputs - // used for anchoring down to commitment using CPFP. - commitTypeAnchors -) - -// String returns that name of the commitment type. -func (c commitType) String() string { - switch c { - case commitTypeLegacy: - return "legacy" - case commitTypeTweakless: - return "tweakless" - case commitTypeAnchors: - return "anchors" +// commitTypeHasAnchors returns whether commitType uses anchor outputs. +func commitTypeHasAnchors(commitType lnrpc.CommitmentType) bool { + switch commitType { + case lnrpc.CommitmentType_ANCHORS, + lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + return true default: - return "invalid" + return false } } -// Args returns the command line flag to supply to enable this commitment type. -func (c commitType) Args() []string { - switch c { - case commitTypeLegacy: +// nodeArgsForCommitType returns the command line flag to supply to enable this +// commitment type. +func nodeArgsForCommitType(commitType lnrpc.CommitmentType) []string { + switch commitType { + case lnrpc.CommitmentType_LEGACY: return []string{"--protocol.legacy.committweak"} - case commitTypeTweakless: + case lnrpc.CommitmentType_STATIC_REMOTE_KEY: return []string{} - case commitTypeAnchors: + case lnrpc.CommitmentType_ANCHORS: return []string{"--protocol.anchors"} + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + return []string{ + "--protocol.anchors", + "--protocol.script-enforced-lease", + } } return nil @@ -251,7 +237,7 @@ func (c commitType) Args() []string { // calcStaticFee calculates appropriate fees for commitment transactions. This // function provides a simple way to allow test balance assertions to take fee // calculations into account. -func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount { +func calcStaticFee(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount { const htlcWeight = input.HTLCWeight var ( feePerKw = chainfee.SatPerKVByte(50000).FeePerKWeight() @@ -263,7 +249,7 @@ func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount { // the value of the two anchors to the resulting fee the initiator // pays. In addition the fee rate is capped at 10 sat/vbyte for anchor // channels. - if c == commitTypeAnchors { + if commitTypeHasAnchors(c) { feePerKw = chainfee.SatPerKVByte( lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000, ).FeePerKWeight() @@ -278,7 +264,7 @@ func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount { // channelCommitType retrieves the active channel commitment type for the given // chan point. func channelCommitType(node *lntest.HarnessNode, - chanPoint *lnrpc.ChannelPoint) (commitType, error) { + chanPoint *lnrpc.ChannelPoint) (lnrpc.CommitmentType, error) { ctxb := context.Background() ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) @@ -291,21 +277,7 @@ func channelCommitType(node *lntest.HarnessNode, for _, c := range channels.Channels { if c.ChannelPoint == txStr(chanPoint) { - switch c.CommitmentType { - - // If the anchor output size is non-zero, we are - // dealing with the anchor type. - case lnrpc.CommitmentType_ANCHORS: - return commitTypeAnchors, nil - - // StaticRemoteKey means it is tweakless, - case lnrpc.CommitmentType_STATIC_REMOTE_KEY: - return commitTypeTweakless, nil - - // Otherwise legacy. - default: - return commitTypeLegacy, nil - } + return c.CommitmentType, nil } } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 56991bd3f04..8283c4d55c6 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2315,21 +2315,24 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // number so we can have the proper witness script to sign and include // within the final witness. theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) - theirPkScript, err := input.CommitScriptToSelf( - theirDelay, keyRing.ToLocalKey, keyRing.RevocationKey, + isRemoteInitiator := !chanState.IsInitiator + var leaseExpiry uint32 + if chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = chanState.ThawHeight + } + theirScript, err := CommitScriptToSelf( + chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, + keyRing.RevocationKey, theirDelay, leaseExpiry, ) if err != nil { return nil, err } - theirWitnessHash, err := input.WitnessScriptHash(theirPkScript) - if err != nil { - return nil, err - } // Since it is the remote breach we are reconstructing, the output going // to us will be a to-remote script with our local params. ourScript, ourDelay, err := CommitScriptToRemote( - chanState.ChanType, keyRing.ToRemoteKey, + chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, + leaseExpiry, ) if err != nil { return nil, err @@ -2347,7 +2350,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, switch { case bytes.Equal(txOut.PkScript, ourScript.PkScript): ourOutpoint.Index = uint32(i) - case bytes.Equal(txOut.PkScript, theirWitnessHash): + case bytes.Equal(txOut.PkScript, theirScript.PkScript): theirOutpoint.Index = uint32(i) } } @@ -2385,9 +2388,9 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, theirSignDesc = &input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: theirPkScript, + WitnessScript: theirScript.WitnessScript, Output: &wire.TxOut{ - PkScript: theirWitnessHash, + PkScript: theirScript.PkScript, Value: int64(theirAmt), }, HashType: txscript.SigHashAll, @@ -2413,8 +2416,10 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // as we'll need it if we're revoking an HTLC output on the // remote commitment transaction, and *they* go to the second // level. - secondLevelWitnessScript, err := input.SecondLevelHtlcScript( + secondLevelScript, err := SecondLevelHtlcScript( + chanState.ChanType, isRemoteInitiator, keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, + leaseExpiry, ) if err != nil { return nil, err @@ -2448,7 +2453,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, Hash: commitHash, Index: uint32(htlc.OutputIndex), }, - SecondLevelWitnessScript: secondLevelWitnessScript, + SecondLevelWitnessScript: secondLevelScript.WitnessScript, IsIncoming: htlc.Incoming, }) } @@ -2972,8 +2977,8 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, // signature can be submitted to the sigPool to generate all the signatures // asynchronously and in parallel. func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, - chanType channeldb.ChannelType, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + chanType channeldb.ChannelType, isRemoteInitiator bool, + leaseExpiry uint32, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, remoteCommitView *commitment) ([]SignJob, chan struct{}, error) { txHash := remoteCommitView.txn.TxHash() @@ -3023,9 +3028,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, Index: uint32(htlc.remoteOutputIndex), } sigJob.Tx, err = CreateHtlcTimeoutTx( - chanType, op, outputAmt, htlc.Timeout, - uint32(remoteChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.ToLocalKey, + chanType, isRemoteInitiator, op, outputAmt, + htlc.Timeout, uint32(remoteChanCfg.CsvDelay), + leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, nil, err @@ -3076,7 +3081,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, Index: uint32(htlc.remoteOutputIndex), } sigJob.Tx, err = CreateHtlcSuccessTx( - chanType, op, outputAmt, uint32(remoteChanCfg.CsvDelay), + chanType, isRemoteInitiator, op, outputAmt, + uint32(remoteChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { @@ -3585,10 +3591,14 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // need to generate signatures of each of them for the remote party's // commitment state. We do so in two phases: first we generate and // submit the set of signature jobs to the worker pool. + var leaseExpiry uint32 + if lc.channelState.ChanType.HasLeaseExpiration() { + leaseExpiry = lc.channelState.ThawHeight + } sigBatch, cancelChan, err := genRemoteHtlcSigJobs( - keyRing, lc.channelState.ChanType, - &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, - newCommitView, + keyRing, lc.channelState.ChanType, !lc.channelState.IsInitiator, + leaseExpiry, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, newCommitView, ) if err != nil { return sig, htlcSigs, nil, err @@ -4071,7 +4081,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, // directly into the pool of workers. func genHtlcSigValidationJobs(localCommitmentView *commitment, keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig, - chanType channeldb.ChannelType, + chanType channeldb.ChannelType, isLocalInitiator bool, leaseExpiry uint32, localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) { txHash := localCommitmentView.txn.TxHash() @@ -4120,9 +4130,10 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, outputAmt := htlc.Amount.ToSatoshis() - htlcFee successTx, err := CreateHtlcSuccessTx( - chanType, op, outputAmt, - uint32(localChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.ToLocalKey, + chanType, isLocalInitiator, op, + outputAmt, uint32(localChanCfg.CsvDelay), + leaseExpiry, keyRing.RevocationKey, + keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -4174,8 +4185,9 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, outputAmt := htlc.Amount.ToSatoshis() - htlcFee timeoutTx, err := CreateHtlcTimeoutTx( - chanType, op, outputAmt, htlc.Timeout, - uint32(localChanCfg.CsvDelay), + chanType, isLocalInitiator, op, + outputAmt, htlc.Timeout, + uint32(localChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { @@ -4389,9 +4401,14 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // As an optimization, we'll generate a series of jobs for the worker // pool to verify each of the HTLc signatures presented. Once // generated, we'll submit these jobs to the worker pool. + var leaseExpiry uint32 + if lc.channelState.ChanType.HasLeaseExpiration() { + leaseExpiry = lc.channelState.ThawHeight + } verifyJobs, err := genHtlcSigValidationJobs( localCommitmentView, keyRing, htlcSigs, - lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + lc.channelState.ChanType, lc.channelState.IsInitiator, + leaseExpiry, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, ) if err != nil { @@ -5459,18 +5476,24 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // First, we'll generate the commitment point and the revocation point // so we can re-construct the HTLC state and also our payment key. + isOurCommit := false keyRing := DeriveCommitmentKeys( - commitPoint, false, chanState.ChanType, + commitPoint, isOurCommit, chanState.ChanType, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) // Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we // had on their commitment transaction. + var leaseExpiry uint32 + if chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = chanState.ThawHeight + } + isRemoteInitiator := !chanState.IsInitiator htlcResolutions, err := extractHtlcResolutions( - chainfee.SatPerKWeight(remoteCommit.FeePerKw), false, signer, - remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, + chainfee.SatPerKWeight(remoteCommit.FeePerKw), isOurCommit, + signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, commitSpend.SpendingTx, - chanState.ChanType, + chanState.ChanType, isRemoteInitiator, leaseExpiry, ) if err != nil { return nil, fmt.Errorf("unable to create htlc "+ @@ -5483,7 +5506,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // locate the output index of our non-delayed output on the commitment // transaction. selfScript, maturityDelay, err := CommitScriptToRemote( - chanState.ChanType, keyRing.ToRemoteKey, + chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, + leaseExpiry, ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ @@ -5690,8 +5714,9 @@ type HtlcResolutions struct { func newOutgoingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, - feePerKw chainfee.SatPerKWeight, csvDelay uint32, - localCommit bool, chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) { + feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, + localCommit, isCommitFromInitiator bool, + chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitTx.TxHash(), @@ -5743,8 +5768,9 @@ func newOutgoingHtlcResolution(signer input.Signer, // With the fee calculated, re-construct the second level timeout // transaction. timeoutTx, err := CreateHtlcTimeoutTx( - chanType, op, secondLevelOutputAmt, htlc.RefundTimeout, - csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey, + chanType, isCommitFromInitiator, op, secondLevelOutputAmt, + htlc.RefundTimeout, csvDelay, leaseExpiry, keyRing.RevocationKey, + keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -5789,16 +5815,13 @@ func newOutgoingHtlcResolution(signer input.Signer, // Finally, we'll generate the script output that the timeout // transaction creates so we can generate the signDesc required to // complete the claim process after a delay period. - htlcSweepScript, err := input.SecondLevelHtlcScript( - keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, + htlcSweepScript, err := SecondLevelHtlcScript( + chanType, isCommitFromInitiator, keyRing.RevocationKey, + keyRing.ToLocalKey, csvDelay, leaseExpiry, ) if err != nil { return nil, err } - htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) - if err != nil { - return nil, err - } localDelayTweak := input.SingleTweakBytes( keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, @@ -5815,9 +5838,9 @@ func newOutgoingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript, + WitnessScript: htlcSweepScript.WitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScriptHash, + PkScript: htlcSweepScript.PkScript, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -5835,8 +5858,9 @@ func newOutgoingHtlcResolution(signer input.Signer, func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, - feePerKw chainfee.SatPerKWeight, csvDelay uint32, localCommit bool, - chanType channeldb.ChannelType) (*IncomingHtlcResolution, error) { + feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, + localCommit, isCommitFromInitiator bool, chanType channeldb.ChannelType) ( + *IncomingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitTx.TxHash(), @@ -5881,8 +5905,8 @@ func newIncomingHtlcResolution(signer input.Signer, htlcFee := HtlcSuccessFee(chanType, feePerKw) secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee successTx, err := CreateHtlcSuccessTx( - chanType, op, secondLevelOutputAmt, csvDelay, - keyRing.RevocationKey, keyRing.ToLocalKey, + chanType, isCommitFromInitiator, op, secondLevelOutputAmt, + csvDelay, leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -5928,16 +5952,13 @@ func newIncomingHtlcResolution(signer input.Signer, // Finally, we'll generate the script that the second-level transaction // creates so we can generate the proper signDesc to sweep it after the // CSV delay has passed. - htlcSweepScript, err := input.SecondLevelHtlcScript( - keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, + htlcSweepScript, err := SecondLevelHtlcScript( + chanType, isCommitFromInitiator, keyRing.RevocationKey, + keyRing.ToLocalKey, csvDelay, leaseExpiry, ) if err != nil { return nil, err } - htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) - if err != nil { - return nil, err - } localDelayTweak := input.SingleTweakBytes( keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, @@ -5953,9 +5974,9 @@ func newIncomingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.DelayBasePoint, SingleTweak: localDelayTweak, - WitnessScript: htlcSweepScript, + WitnessScript: htlcSweepScript.WitnessScript, Output: &wire.TxOut{ - PkScript: htlcSweepScriptHash, + PkScript: htlcSweepScript.PkScript, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -5993,8 +6014,8 @@ func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint { func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - commitTx *wire.MsgTx, chanType channeldb.ChannelType) ( - *HtlcResolutions, error) { + commitTx *wire.MsgTx, chanType channeldb.ChannelType, + isCommitFromInitiator bool, leaseExpiry uint32) (*HtlcResolutions, error) { // TODO(roasbeef): don't need to swap csv delay? dustLimit := remoteChanCfg.DustLimit @@ -6026,8 +6047,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, // as we can satisfy the contract. ihr, err := newIncomingHtlcResolution( signer, localChanCfg, commitTx, &htlc, - keyRing, feePerKw, uint32(csvDelay), ourCommit, - chanType, + keyRing, feePerKw, uint32(csvDelay), leaseExpiry, + ourCommit, isCommitFromInitiator, chanType, ) if err != nil { return nil, err @@ -6039,7 +6060,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, ohr, err := newOutgoingHtlcResolution( signer, localChanCfg, commitTx, &htlc, keyRing, - feePerKw, uint32(csvDelay), ourCommit, chanType, + feePerKw, uint32(csvDelay), leaseExpiry, ourCommit, + isCommitFromInitiator, chanType, ) if err != nil { return nil, err @@ -6178,13 +6200,14 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) - selfScript, err := input.CommitScriptToSelf( - csvTimeout, keyRing.ToLocalKey, keyRing.RevocationKey, - ) - if err != nil { - return nil, err + var leaseExpiry uint32 + if chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = chanState.ThawHeight } - payToUsScriptHash, err := input.WitnessScriptHash(selfScript) + toLocalScript, err := CommitScriptToSelf( + chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey, + keyRing.RevocationKey, csvTimeout, leaseExpiry, + ) if err != nil { return nil, err } @@ -6197,7 +6220,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, delayOut *wire.TxOut ) for i, txOut := range commitTx.TxOut { - if !bytes.Equal(payToUsScriptHash, txOut.PkScript) { + if !bytes.Equal(toLocalScript.PkScript, txOut.PkScript) { continue } @@ -6223,7 +6246,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.DelayBasePoint, SingleTweak: keyRing.LocalCommitKeyTweak, - WitnessScript: selfScript, + WitnessScript: toLocalScript.WitnessScript, Output: &wire.TxOut{ PkScript: delayOut.PkScript, Value: localBalance, @@ -6244,6 +6267,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, chainfee.SatPerKWeight(localCommit.FeePerKw), true, signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, commitTx, chanState.ChanType, + chanState.IsInitiator, leaseExpiry, ) if err != nil { return nil, err diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 96960f9dd11..03cfd224026 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -190,16 +190,81 @@ type ScriptInfo struct { WitnessScript []byte } -// CommitScriptToRemote creates the script that will pay to the non-owner of -// the commitment transaction, adding a delay to the script based on the -// channel type. The second return value is the CSV deleay of the output -// script, what must be satisfied in order to spend the output. -func CommitScriptToRemote(chanType channeldb.ChannelType, - key *btcec.PublicKey) (*ScriptInfo, uint32, error) { +// CommitScriptToSelf constructs the public key script for the output on the +// commitment transaction paying to the "owner" of said commitment transaction. +// The `initiator` argument should correspond to the owner of the commitment +// tranasction which we are generating the to_local script for. If the other +// party learns of the preimage to the revocation hash, then they can claim all +// the settled funds in the channel, plus the unsettled funds. +func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, + selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) ( + *ScriptInfo, error) { + + var ( + toLocalRedeemScript []byte + err error + ) + switch { + // If we are the initiator of a leased channel, then we have an + // additional CLTV requirement in addition to the usual CSV requirement. + case initiator && chanType.HasLeaseExpiration(): + toLocalRedeemScript, err = input.LeaseCommitScriptToSelf( + selfKey, revokeKey, csvDelay, leaseExpiry, + ) + + default: + toLocalRedeemScript, err = input.CommitScriptToSelf( + csvDelay, selfKey, revokeKey, + ) + } + if err != nil { + return nil, err + } + + toLocalScriptHash, err := input.WitnessScriptHash(toLocalRedeemScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil +} + +// CommitScriptToRemote derives the appropriate to_remote script based on the +// channel's commitment type. The `initiator` argument should correspond to the +// owner of the commitment tranasction which we are generating the to_remote +// script for. The second return value is the CSV delay of the output script, +// what must be satisfied in order to spend the output. +func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, + key *btcec.PublicKey, leaseExpiry uint32) (*ScriptInfo, uint32, error) { + + switch { + // If we are not the initiator of a leased channel, then the remote + // party has an additional CLTV requirement in addition to the 1 block + // CSV requirement. + case chanType.HasLeaseExpiration() && !initiator: + script, err := input.LeaseCommitScriptToRemoteConfirmed( + key, leaseExpiry, + ) + if err != nil { + return nil, 0, err + } + + p2wsh, err := input.WitnessScriptHash(script) + if err != nil { + return nil, 0, err + } + + return &ScriptInfo{ + PkScript: p2wsh, + WitnessScript: script, + }, 1, nil // If this channel type has anchors, we derive the delayed to_remote // script. - if chanType.HasAnchors() { + case chanType.HasAnchors(): script, err := input.CommitScriptToRemoteConfirmed(key) if err != nil { return nil, 0, err @@ -214,20 +279,22 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, PkScript: p2wsh, WitnessScript: script, }, 1, nil - } - // Otherwise the to_remote will be a simple p2wkh. - p2wkh, err := input.CommitScriptUnencumbered(key) - if err != nil { - return nil, 0, err - } + default: + // Otherwise the to_remote will be a simple p2wkh. + p2wkh, err := input.CommitScriptUnencumbered(key) + if err != nil { + return nil, 0, err + } - // Since this is a regular P2WKH, the WitnessScipt and PkScript should - // both be set to the script hash. - return &ScriptInfo{ - WitnessScript: p2wkh, - PkScript: p2wkh, - }, 0, nil + // Since this is a regular P2WKH, the WitnessScipt and PkScript + // should both be set to the script hash. + return &ScriptInfo{ + WitnessScript: p2wkh, + PkScript: p2wkh, + }, 0, nil + + } } // HtlcSigHashType returns the sighash type to use for HTLC success and timeout @@ -268,6 +335,49 @@ func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 { return 0 } +// SecondLevelHtlcScript derives the appropriate second level HTLC script based +// on the channel's commitment type. It is the uniform script that's used as the +// output for the second-level HTLC transactions. The second level transaction +// act as a sort of covenant, ensuring that a 2-of-2 multi-sig output can only +// be spent in a particular way, and to a particular output. The `initiator` +// argument should correspond to the owner of the commitment tranasction which +// we are generating the to_local script for. +func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, + revocationKey, delayKey *btcec.PublicKey, + csvDelay, leaseExpiry uint32) (*ScriptInfo, error) { + + var ( + witnessScript []byte + err error + ) + switch { + // If we are the initiator of a leased channel, then we have an + // additional CLTV requirement in addition to the usual CSV requirement. + case initiator && chanType.HasLeaseExpiration(): + witnessScript, err = input.LeaseSecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, leaseExpiry, + ) + + default: + witnessScript, err = input.SecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + } + if err != nil { + return nil, err + } + + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: pkScript, + WitnessScript: witnessScript, + }, nil +} + // CommitWeight returns the base commitment weight before adding HTLCs. func CommitWeight(chanType channeldb.ChannelType) int64 { // If this commitment has anchors, it will be slightly heavier. @@ -499,19 +609,23 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // CreateCommitTx with parameters matching the perspective, to generate // a new commitment transaction with all the latest unsettled/un-timed // out HTLCs. + var leaseExpiry uint32 + if cb.chanState.ChanType.HasLeaseExpiration() { + leaseExpiry = cb.chanState.ThawHeight + } if isOurs { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), - numHTLCs, + numHTLCs, cb.chanState.IsInitiator, leaseExpiry, ) } else { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), - numHTLCs, + numHTLCs, !cb.chanState.IsInitiator, leaseExpiry, ) } if err != nil { @@ -610,27 +724,22 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // output paying to the "owner" of the commitment transaction which can be // spent after a relative block delay or revocation event, and a remote output // paying the counterparty within the channel, which can be spent immediately -// or after a delay depending on the commitment type.. +// or after a delay depending on the commitment type. The `initiator` argument +// should correspond to the owner of the commitment tranasction we are creating. func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, amountToLocal, amountToRemote btcutil.Amount, - numHTLCs int64) (*wire.MsgTx, error) { + numHTLCs int64, initiator bool, leaseExpiry uint32) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. // This output has 2 main redemption clauses: either we can redeem the // output after a relative block delay, or the remote node can claim // the funds with the revocation key if we broadcast a revoked // commitment transaction. - toLocalRedeemScript, err := input.CommitScriptToSelf( - uint32(localChanCfg.CsvDelay), keyRing.ToLocalKey, - keyRing.RevocationKey, - ) - if err != nil { - return nil, err - } - toLocalScriptHash, err := input.WitnessScriptHash( - toLocalRedeemScript, + toLocalScript, err := CommitScriptToSelf( + chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey, + uint32(localChanCfg.CsvDelay), leaseExpiry, ) if err != nil { return nil, err @@ -638,7 +747,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // Next, we create the script paying to the remote. toRemoteScript, _, err := CommitScriptToRemote( - chanType, keyRing.ToRemoteKey, + chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, ) if err != nil { return nil, err @@ -654,7 +763,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, localOutput := amountToLocal >= localChanCfg.DustLimit if localOutput { commitTx.AddTxOut(&wire.TxOut{ - PkScript: toLocalScriptHash, + PkScript: toLocalScript.PkScript, Value: int64(amountToLocal), }) } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 2b24178c44d..8ea4be4955e 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -1,6 +1,7 @@ package lnwallet import ( + "errors" "net" "sync" @@ -34,8 +35,40 @@ const ( // requires second-level HTLC transactions to be signed using a // zero-fee. CommitmentTypeAnchorsZeroFeeHtlcTx + + // CommitmentTypeScriptEnforcedLease is a commitment type that builds + // upon CommitmentTypeTweakless and CommitmentTypeAnchorsZeroFeeHtlcTx, + // which in addition requires a CLTV clause to spend outputs paying to + // the channel initiator. This is intended for use on leased channels to + // guarantee that the channel initiator has no incentives to close a + // leased channel before its maturity date. + CommitmentTypeScriptEnforcedLease ) +// HasStaticRemoteKey returns whether the commitment type supports remote +// outputs backed by static keys. +func (c CommitmentType) HasStaticRemoteKey() bool { + switch c { + case CommitmentTypeTweakless, + CommitmentTypeAnchorsZeroFeeHtlcTx, + CommitmentTypeScriptEnforcedLease: + return true + default: + return false + } +} + +// HasAnchors returns whether the commitment type supports anchor outputs. +func (c CommitmentType) HasAnchors() bool { + switch c { + case CommitmentTypeAnchorsZeroFeeHtlcTx, + CommitmentTypeScriptEnforcedLease: + return true + default: + return false + } +} + // String returns the name of the CommitmentType. func (c CommitmentType) String() string { switch c { @@ -45,6 +78,8 @@ func (c CommitmentType) String() string { return "tweakless" case CommitmentTypeAnchorsZeroFeeHtlcTx: return "anchors-zero-fee-second-level" + case CommitmentTypeScriptEnforcedLease: + return "script-enforced-lease" default: return "invalid" } @@ -188,8 +223,8 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // Based on the channel type, we determine the initial commit weight // and fee. commitWeight := int64(input.CommitWeight) - if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { - commitWeight = input.AnchorCommitWeight + if commitType.HasAnchors() { + commitWeight = int64(input.AnchorCommitWeight) } commitFee := commitFeePerKw.FeeForWeight(commitWeight) @@ -201,7 +236,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // The total fee paid by the initiator will be the commitment fee in // addition to the two anchor outputs. feeMSat := lnwire.NewMSatFromSatoshis(commitFee) - if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { + if commitType.HasAnchors() { feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) } @@ -285,8 +320,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 { // Both the tweakless type and the anchor type is tweakless, // hence set the bit. - if commitType == CommitmentTypeTweakless || - commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { + if commitType.HasStaticRemoteKey() { chanType |= channeldb.SingleFunderTweaklessBit } else { chanType |= channeldb.SingleFunderBit @@ -322,14 +356,20 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // We are adding anchor outputs to our commitment. We only support this // in combination with zero-fee second-levels HTLCs. - if commitType == CommitmentTypeAnchorsZeroFeeHtlcTx { + if commitType.HasAnchors() { chanType |= channeldb.AnchorOutputsBit chanType |= channeldb.ZeroHtlcTxFeeBit } - // If the channel is meant to be frozen, then we'll set the frozen bit - // now so once the channel is open, it can be interpreted properly. - if thawHeight != 0 { + // Set the appropriate LeaseExpiration/Frozen bit based on the + // reservation parameters. + if commitType == CommitmentTypeScriptEnforcedLease { + if thawHeight == 0 { + return nil, errors.New("missing absolute expiration " + + "for script enforced lease commitment type") + } + chanType |= channeldb.LeaseExpirationBit + } else if thawHeight > 0 { chanType |= channeldb.FrozenBit } @@ -684,6 +724,16 @@ func (r *ChannelReservation) Capacity() btcutil.Amount { return r.partialState.Capacity } +// LeaseExpiry returns the absolute expiration height for a leased channel using +// the script enforced commitment type. A zero value is returned when the +// channel is not using a script enforced lease commitment type. +func (r *ChannelReservation) LeaseExpiry() uint32 { + if !r.partialState.ChanType.HasLeaseExpiration() { + return 0 + } + return r.partialState.ThawHeight +} + // Cancel abandons this channel reservation. This method should be called in // the scenario that communications with the counterparty break down. Upon // cancellation, all resources previously reserved for this pending payment diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index bd048b2c0ff..af7f538ac8c 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -116,6 +116,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( bobDustLimit := btcutil.Amount(1300) csvTimeoutAlice := uint32(5) csvTimeoutBob := uint32(4) + isAliceInitiator := true prevOut := &wire.OutPoint{ Hash: chainhash.Hash(testHdSeed), @@ -220,7 +221,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, - bobCommitPoint, *fundingTxIn, chanType, + bobCommitPoint, *fundingTxIn, chanType, isAliceInitiator, 0, ) if err != nil { return nil, nil, nil, err @@ -315,7 +316,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( FundingOutpoint: *prevOut, ShortChannelID: shortChanID, ChanType: chanType, - IsInitiator: true, + IsInitiator: isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: bobCommitPoint, RevocationProducer: alicePreimageProducer, @@ -333,7 +334,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( FundingOutpoint: *prevOut, ShortChannelID: shortChanID, ChanType: chanType, - IsInitiator: false, + IsInitiator: !isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: aliceCommitPoint, RevocationProducer: bobPreimageProducer, diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 861fdebcea1..ce2eb9e80d3 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -8,7 +8,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/input" ) const ( @@ -45,9 +44,10 @@ var ( // In order to spend the HTLC output, the witness for the passed transaction // should be: // * <0> -func CreateHtlcSuccessTx(chanType channeldb.ChannelType, - htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay uint32, - revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) { +func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, + htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay, + leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) ( + *wire.MsgTx, error) { // Create a version two transaction (as the success version of this // spends an output with a CSV timeout). @@ -65,12 +65,10 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, // Next, we'll generate the script used as the output for all second // level HTLC which forces a covenant w.r.t what can be done with all // HTLC outputs. - witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey, - csvDelay) - if err != nil { - return nil, err - } - pkScript, err := input.WitnessScriptHash(witnessScript) + script, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) if err != nil { return nil, err } @@ -79,7 +77,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, // required fees), paying to the timeout script. successTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: script.PkScript, }) return successTx, nil @@ -101,9 +99,9 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, // NOTE: The passed amount for the HTLC should take into account the required // fee rate at the time the HTLC was created. The fee should be able to // entirely pay for this (tiny: 1-in 1-out) transaction. -func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, +func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, - cltvExpiry, csvDelay uint32, + cltvExpiry, csvDelay, leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) { // Create a version two transaction (as the success version of this @@ -117,6 +115,8 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // sequence number based on the channel type. txin := &wire.TxIn{ PreviousOutPoint: htlcOutput, + SignatureScript: []byte{}, + Witness: [][]byte{}, Sequence: HtlcSecondLevelInputSequence(chanType), } timeoutTx.AddTxIn(txin) @@ -124,12 +124,10 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // Next, we'll generate the script used as the output for all second // level HTLC which forces a covenant w.r.t what can be done with all // HTLC outputs. - witnessScript, err := input.SecondLevelHtlcScript(revocationKey, delayKey, - csvDelay) - if err != nil { - return nil, err - } - pkScript, err := input.WitnessScriptHash(witnessScript) + script, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) if err != nil { return nil, err } @@ -138,7 +136,7 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, // required fees), paying to the regular second level HTLC script. timeoutTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: pkScript, + PkScript: script.PkScript, }) return timeoutTx, nil diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 696328cdb2e..d923169360b 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -570,7 +570,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { } commitmentTx, err := CreateCommitTx( channelType, *fakeFundingTxIn, keyRing, aliceChanCfg, - bobChanCfg, channelBalance, channelBalance, 0, + bobChanCfg, channelBalance, channelBalance, 0, true, 0, ) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) @@ -886,7 +886,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp remoteCommitTx, localCommitTx, err := CreateCommitmentTxns( remoteBalance, localBalance-commitFee, &remoteCfg, &localCfg, remoteCommitPoint, - localCommitPoint, *fundingTxIn, chanType, + localCommitPoint, *fundingTxIn, chanType, true, 0, ) require.NoError(t, err) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 1b1b40b6d77..39a41455990 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -794,7 +794,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg // funding tx ready, so this will always pass. We'll do another check // when the PSBT has been verified. isPublic := req.Flags&lnwire.FFAnnounceChannel != 0 - hasAnchors := req.CommitType == CommitmentTypeAnchorsZeroFeeHtlcTx + hasAnchors := req.CommitType.HasAnchors() err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors) if err != nil { fundingIntent.Cancel() @@ -1189,8 +1189,8 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourChanCfg, theirChanCfg *channeldb.ChannelConfig, localCommitPoint, remoteCommitPoint *btcec.PublicKey, - fundingTxIn wire.TxIn, chanType channeldb.ChannelType) ( - *wire.MsgTx, *wire.MsgTx, error) { + fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool, + leaseExpiry uint32) (*wire.MsgTx, *wire.MsgTx, error) { localCommitmentKeys := DeriveCommitmentKeys( localCommitPoint, true, chanType, ourChanCfg, theirChanCfg, @@ -1201,7 +1201,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, - theirChanCfg, localBalance, remoteBalance, 0, + theirChanCfg, localBalance, remoteBalance, 0, initiator, + leaseExpiry, ) if err != nil { return nil, nil, err @@ -1214,7 +1215,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, theirCommitTx, err := CreateCommitTx( chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, - ourChanCfg, remoteBalance, localBalance, 0, + ourChanCfg, remoteBalance, localBalance, 0, !initiator, + leaseExpiry, ) if err != nil { return nil, nil, err @@ -1453,12 +1455,17 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { // With the funding tx complete, create both commitment transactions. localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis() remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis() + var leaseExpiry uint32 + if pendingReservation.partialState.ChanType.HasLeaseExpiration() { + leaseExpiry = pendingReservation.partialState.ThawHeight + } ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, ourContribution.ChannelConfig, theirContribution.ChannelConfig, ourContribution.FirstCommitmentPoint, theirContribution.FirstCommitmentPoint, fundingTxIn, pendingReservation.partialState.ChanType, + pendingReservation.partialState.IsInitiator, leaseExpiry, ) if err != nil { req.err <- err @@ -1820,6 +1827,10 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // remote node's commitment transactions. localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis() remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis() + var leaseExpiry uint32 + if pendingReservation.partialState.ChanType.HasLeaseExpiration() { + leaseExpiry = pendingReservation.partialState.ThawHeight + } ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, pendingReservation.ourContribution.ChannelConfig, @@ -1827,6 +1838,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { pendingReservation.ourContribution.FirstCommitmentPoint, pendingReservation.theirContribution.FirstCommitmentPoint, *fundingTxIn, pendingReservation.partialState.ChanType, + pendingReservation.partialState.IsInitiator, leaseExpiry, ) if err != nil { req.err <- err diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index f88a1a08fba..ac8f447b524 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -1,11 +1,11 @@ package lnwire import ( - "fmt" "io" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/tlv" ) // AcceptChannel is the message Bob sends to Alice after she initiates the @@ -94,6 +94,16 @@ type AcceptChannel struct { // and its length followed by the script will be written if it is set. UpfrontShutdownScript DeliveryAddress + // ChannelType is the explicit channel type the initiator wishes to + // open. + ChannelType *ChannelType + + // LeaseExpiry represents the absolute expiration height of a channel + // lease. This is a custom TLV record that will only apply when a leased + // channel is being opened using the script enforced lease commitment + // type. + LeaseExpiry *LeaseExpiry + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -116,11 +126,14 @@ var _ Message = (*AcceptChannel)(nil) // // This is part of the lnwire.Message interface. func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error { - // Since the upfront script is encoded as a TLV record, concatenate it - // with the ExtraData, and write them as one. - tlvRecords, err := packShutdownScript( - a.UpfrontShutdownScript, a.ExtraData, - ) + recordProducers := []tlv.RecordProducer{&a.UpfrontShutdownScript} + if a.ChannelType != nil { + recordProducers = append(recordProducers, a.ChannelType) + } + if a.LeaseExpiry != nil { + recordProducers = append(recordProducers, a.LeaseExpiry) + } + err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err } @@ -140,7 +153,7 @@ func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error { a.DelayedPaymentPoint, a.HtlcPoint, a.FirstCommitmentPoint, - tlvRecords, + a.ExtraData, ) } @@ -179,72 +192,30 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { return err } - a.UpfrontShutdownScript, a.ExtraData, err = parseShutdownScript( - tlvRecords, + // Next we'll parse out the set of known records, keeping the raw tlv + // bytes untouched to ensure we don't drop any bytes erroneously. + var ( + chanType ChannelType + leaseExpiry LeaseExpiry + ) + typeMap, err := tlvRecords.ExtractRecords( + &a.UpfrontShutdownScript, &chanType, &leaseExpiry, ) if err != nil { return err } - return nil -} - -// packShutdownScript takes an upfront shutdown script and an opaque data blob -// and concatenates them. -func packShutdownScript(addr DeliveryAddress, extraData ExtraOpaqueData) ( - ExtraOpaqueData, error) { - - // We'll always write the upfront shutdown script record, regardless of - // the script being empty. - var tlvRecords ExtraOpaqueData - - // Pack it into a data blob as a TLV record. - err := tlvRecords.PackRecords(addr.NewRecord()) - if err != nil { - return nil, fmt.Errorf("unable to pack upfront shutdown "+ - "script as TLV record: %v", err) - } - - // Concatenate the remaining blob with the shutdown script record. - tlvRecords = append(tlvRecords, extraData...) - return tlvRecords, nil -} - -// parseShutdownScript reads and extract the upfront shutdown script from the -// passe data blob. It returns the script, if any, and the remainder of the -// data blob. -// -// This can be used to parse extra data for the OpenChannel and AcceptChannel -// messages, where the shutdown script is mandatory if extra TLV data is -// present. -func parseShutdownScript(tlvRecords ExtraOpaqueData) (DeliveryAddress, - ExtraOpaqueData, error) { - - // If no TLV data is present there can't be any script available. - if len(tlvRecords) == 0 { - return nil, tlvRecords, nil - } - - // Otherwise the shutdown script MUST be present. - var addr DeliveryAddress - tlvs, err := tlvRecords.ExtractRecords(addr.NewRecord()) - if err != nil { - return nil, nil, err + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil { + a.ChannelType = &chanType } - - // Not among TLV records, this means the data was invalid. - if _, ok := tlvs[DeliveryAddrType]; !ok { - return nil, nil, fmt.Errorf("no shutdown script in non-empty " + - "data blob") + if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { + a.LeaseExpiry = &leaseExpiry } - // Now that we have retrieved the address (which can be zero-length), - // we'll remove the bytes encoding it from the TLV data before - // returning it. - addrLen := len(addr) - tlvRecords = tlvRecords[addrLen+2:] + a.ExtraData = tlvRecords - return addr, tlvRecords, nil + return nil } // MsgType returns the MessageType code which uniquely identifies this message diff --git a/lnwire/channel_type.go b/lnwire/channel_type.go new file mode 100644 index 00000000000..a0696048bef --- /dev/null +++ b/lnwire/channel_type.go @@ -0,0 +1,58 @@ +package lnwire + +import ( + "io" + + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // ChannelTypeRecordType is the type of the experimental record used + // to denote which channel type is being negotiated. + ChannelTypeRecordType tlv.Type = 1 +) + +// ChannelType represents a specific channel type as a set of feature bits that +// comprise it. +type ChannelType RawFeatureVector + +// featureBitLen returns the length in bytes of the encoded feature bits. +func (c ChannelType) featureBitLen() uint64 { + fv := RawFeatureVector(c) + return uint64(fv.SerializeSize()) +} + +// Record returns a TLV record that can be used to encode/decode the channel +// type from a given TLV stream. +func (c *ChannelType) Record() tlv.Record { + return tlv.MakeDynamicRecord( + ChannelTypeRecordType, c, c.featureBitLen, channelTypeEncoder, + channelTypeDecoder, + ) +} + +// channelTypeEncoder is a custom TLV encoder for the ChannelType record. +func channelTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*ChannelType); ok { + // Encode the feature bits as a byte slice without its length + // prepended, as that's already taken care of by the TLV record. + fv := RawFeatureVector(*v) + return fv.encode(w, fv.SerializeSize(), 8) + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelType") +} + +// channelTypeDecoder is a custom TLV decoder for the ChannelType record. +func channelTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if v, ok := val.(*ChannelType); ok { + fv := NewRawFeatureVector() + if err := fv.decode(r, int(l), 8); err != nil { + return err + } + *v = ChannelType(*fv) + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelType") +} diff --git a/lnwire/channel_type_test.go b/lnwire/channel_type_test.go new file mode 100644 index 00000000000..dd8e02439aa --- /dev/null +++ b/lnwire/channel_type_test.go @@ -0,0 +1,28 @@ +package lnwire + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestChannelTypeEncodeDecode tests that we're able to properly encode and +// decode channel types within TLV streams. +func TestChannelTypeEncodeDecode(t *testing.T) { + t.Parallel() + + chanType := ChannelType(*NewRawFeatureVector( + StaticRemoteKeyRequired, + AnchorsZeroFeeHtlcTxRequired, + )) + + var extraData ExtraOpaqueData + require.NoError(t, extraData.PackRecords(&chanType)) + + var chanType2 ChannelType + tlvs, err := extraData.ExtractRecords(&chanType2) + require.NoError(t, err) + + require.Contains(t, tlvs, ChannelTypeRecordType) + require.Equal(t, chanType, chanType2) +} diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 22fd20bd837..95ca1e384e1 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -2,6 +2,7 @@ package lnwire import ( "bytes" + "fmt" "io" "io/ioutil" @@ -50,7 +51,17 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error { // PackRecords attempts to encode the set of tlv records into the target // ExtraOpaqueData instance. The records will be encoded as a raw TLV stream // and stored within the backing slice pointer. -func (e *ExtraOpaqueData) PackRecords(records ...tlv.Record) error { +func (e *ExtraOpaqueData) PackRecords(recordProducers ...tlv.RecordProducer) error { + // First, assemble all the records passed in in series. + records := make([]tlv.Record, 0, len(recordProducers)) + for _, producer := range recordProducers { + records = append(records, producer.Record()) + } + + // Ensure that the set of records are sorted before we encode them into + // the stream, to ensure they're canonical. + tlv.SortRecords(records) + tlvStream, err := tlv.NewStream(records...) if err != nil { return err @@ -70,9 +81,15 @@ func (e *ExtraOpaqueData) PackRecords(records ...tlv.Record) error { // it were a tlv stream. The set of raw parsed types is returned, and any // passed records (if found in the stream) will be parsed into the proper // tlv.Record. -func (e *ExtraOpaqueData) ExtractRecords(records ...tlv.Record) ( +func (e *ExtraOpaqueData) ExtractRecords(recordProducers ...tlv.RecordProducer) ( tlv.TypeMap, error) { + // First, assemble all the records passed in in series. + records := make([]tlv.Record, 0, len(recordProducers)) + for _, producer := range recordProducers { + records = append(records, producer.Record()) + } + extraBytesReader := bytes.NewReader(*e) tlvStream, err := tlv.NewStream(records...) @@ -82,3 +99,19 @@ func (e *ExtraOpaqueData) ExtractRecords(records ...tlv.Record) ( return tlvStream.DecodeWithParsedTypes(extraBytesReader) } + +// EncodeMessageExtraData encodes the given recordProducers into the given +// extraData. +func EncodeMessageExtraData(extraData *ExtraOpaqueData, + recordProducers ...tlv.RecordProducer) error { + + // Treat extraData as a mutable reference. + if extraData == nil { + return fmt.Errorf("extra data cannot be nil") + } + + // Pack in the series of TLV records into this message. The order we + // pass them in doesn't matter, as the method will ensure that things + // are all properly sorted. + return extraData.PackRecords(recordProducers...) +} diff --git a/lnwire/extra_bytes_test.go b/lnwire/extra_bytes_test.go index 39271d6aa29..88ffcc307ca 100644 --- a/lnwire/extra_bytes_test.go +++ b/lnwire/extra_bytes_test.go @@ -86,6 +86,14 @@ func TestExtraOpaqueDataEncodeDecode(t *testing.T) { } } +type recordProducer struct { + record tlv.Record +} + +func (r *recordProducer) Record() tlv.Record { + return r.record +} + // TestExtraOpaqueDataPackUnpackRecords tests that we're able to pack a set of // tlv.Records into a stream, and unpack them on the other side to obtain the // same set of records. @@ -102,23 +110,23 @@ func TestExtraOpaqueDataPackUnpackRecords(t *testing.T) { hop1 uint32 = 99 hop2 uint32 ) - testRecords := []tlv.Record{ - tlv.MakePrimitiveRecord(type1, &channelType1), - tlv.MakePrimitiveRecord(type2, &hop1), + testRecordsProducers := []tlv.RecordProducer{ + &recordProducer{tlv.MakePrimitiveRecord(type1, &channelType1)}, + &recordProducer{tlv.MakePrimitiveRecord(type2, &hop1)}, } // Now that we have our set of sample records and types, we'll encode // them into the passed ExtraOpaqueData instance. var extraBytes ExtraOpaqueData - if err := extraBytes.PackRecords(testRecords...); err != nil { + if err := extraBytes.PackRecords(testRecordsProducers...); err != nil { t.Fatalf("unable to pack records: %v", err) } // We'll now simulate decoding these types _back_ into records on the // other side. - newRecords := []tlv.Record{ - tlv.MakePrimitiveRecord(type1, &channelType2), - tlv.MakePrimitiveRecord(type2, &hop2), + newRecords := []tlv.RecordProducer{ + &recordProducer{tlv.MakePrimitiveRecord(type1, &channelType2)}, + &recordProducer{tlv.MakePrimitiveRecord(type2, &hop2)}, } typeMap, err := extraBytes.ExtractRecords(newRecords...) if err != nil { diff --git a/lnwire/features.go b/lnwire/features.go index 9dfa07ee1e3..c800563c1e6 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -139,6 +139,44 @@ const ( // sender-generated preimages according to BOLT XX. AMPOptional FeatureBit = 31 + // ExplicitChannelTypeRequired is a required bit that denotes that a + // connection established with this node is to use explicit channel + // commitment types for negotiation instead of the existing implicit + // negotiation methods. With this bit, there is no longer a "default" + // implicit channel commitment type, allowing a connection to + // open/maintain types of several channels over its lifetime. + // + // TODO: Decide on actual feature bit value. + ExplicitChannelTypeRequired = 2020 + + // ExplicitChannelTypeOptional is an optional bit that denotes that a + // connection established with this node is to use explicit channel + // commitment types for negotiation instead of the existing implicit + // negotiation methods. With this bit, there is no longer a "default" + // implicit channel commitment type, allowing a connection to + // open/maintain types of several channels over its lifetime. + // + // TODO: Decide on actual feature bit value. + ExplicitChannelTypeOptional = 2021 + + // ScriptEnforcedLeaseOptional is an optional feature bit that signals + // that the node requires channels having zero-fee second-level HTLC + // transactions, which also imply anchor commitments, along with an + // additional CLTV constraint of a channel lease's expiration height + // applied to all outputs that pay directly to the channel initiator. + // + // TODO: Decide on actual feature bit value. + ScriptEnforcedLeaseRequired FeatureBit = 2022 + + // ScriptEnforcedLeaseOptional is a required feature bit that signals + // that the node requires channels having zero-fee second-level HTLC + // transactions, which also imply anchor commitments, along with an + // additional CLTV constraint of a channel lease's expiration height + // applied to all outputs that pay directly to the channel initiator. + // + // TODO: Decide on actual feature bit value. + ScriptEnforcedLeaseOptional FeatureBit = 2023 + // maxAllowedSize is a maximum allowed size of feature vector. // // NOTE: Within the protocol, the maximum allowed message size is 65535 @@ -184,6 +222,10 @@ var Features = map[FeatureBit]string{ WumboChannelsOptional: "wumbo-channels", AMPRequired: "amp", AMPOptional: "amp", + ExplicitChannelTypeOptional: "explicit-commitment-type", + ExplicitChannelTypeRequired: "explicit-commitment-type", + ScriptEnforcedLeaseRequired: "script-enforced-lease", + ScriptEnforcedLeaseOptional: "script-enforced-lease", } // RawFeatureVector represents a set of feature bits as defined in BOLT-09. A @@ -192,19 +234,51 @@ var Features = map[FeatureBit]string{ // can be serialized and deserialized to/from a byte representation that is // transmitted in Lightning network messages. type RawFeatureVector struct { - features map[FeatureBit]bool + features map[FeatureBit]struct{} } // NewRawFeatureVector creates a feature vector with all of the feature bits // given as arguments enabled. func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector { - fv := &RawFeatureVector{features: make(map[FeatureBit]bool)} + fv := &RawFeatureVector{features: make(map[FeatureBit]struct{})} for _, bit := range bits { fv.Set(bit) } return fv } +// IsEmpty returns whether the feature vector contains any feature bits. +func (fv RawFeatureVector) IsEmpty() bool { + return len(fv.features) == 0 +} + +// OnlyContains determines whether only the specified feature bits are found. +func (fv RawFeatureVector) OnlyContains(bits ...FeatureBit) bool { + if len(bits) != len(fv.features) { + return false + } + for _, bit := range bits { + if !fv.IsSet(bit) { + return false + } + } + return true +} + +// Equals determines whether two features vectors contain exactly the same +// features. +func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool { + if len(fv.features) != len(other.features) { + return false + } + for bit := range fv.features { + if _, ok := other.features[bit]; !ok { + return false + } + } + return true +} + // Merges sets all feature bits in other on the receiver's feature vector. func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error { for bit := range other.features { @@ -227,12 +301,13 @@ func (fv *RawFeatureVector) Clone() *RawFeatureVector { // IsSet returns whether a particular feature bit is enabled in the vector. func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool { - return fv.features[feature] + _, ok := fv.features[feature] + return ok } // Set marks a feature as enabled in the vector. func (fv *RawFeatureVector) Set(feature FeatureBit) { - fv.features[feature] = true + fv.features[feature] = struct{}{} } // SafeSet sets the chosen feature bit in the feature vector, but returns an diff --git a/lnwire/features_test.go b/lnwire/features_test.go index 3eed2b1b403..d4be13d4cfa 100644 --- a/lnwire/features_test.go +++ b/lnwire/features_test.go @@ -353,3 +353,47 @@ func TestFeatures(t *testing.T) { }) } } + +func TestRawFeatureVectorOnlyContains(t *testing.T) { + t.Parallel() + + features := []FeatureBit{ + StaticRemoteKeyOptional, + AnchorsZeroFeeHtlcTxOptional, + ExplicitChannelTypeRequired, + } + fv := NewRawFeatureVector(features...) + require.True(t, fv.OnlyContains(features...)) + require.False(t, fv.OnlyContains(features[:1]...)) +} + +func TestEqualRawFeatureVectors(t *testing.T) { + t.Parallel() + + a := NewRawFeatureVector( + StaticRemoteKeyOptional, + AnchorsZeroFeeHtlcTxOptional, + ExplicitChannelTypeRequired, + ) + b := a.Clone() + require.True(t, a.Equals(b)) + + b.Unset(ExplicitChannelTypeRequired) + require.False(t, a.Equals(b)) + + b.Set(ExplicitChannelTypeOptional) + require.False(t, a.Equals(b)) +} + +func TestIsEmptyFeatureVector(t *testing.T) { + t.Parallel() + + fv := NewRawFeatureVector() + require.True(t, fv.IsEmpty()) + + fv.Set(StaticRemoteKeyOptional) + require.False(t, fv.IsEmpty()) + + fv.Unset(StaticRemoteKeyOptional) + require.True(t, fv.IsEmpty()) +} diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 14082bfe93f..43cfe5e1eb7 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -18,8 +18,8 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/tor" + "github.com/stretchr/testify/assert" ) var ( @@ -284,9 +284,7 @@ func TestLightningWireProtocol(t *testing.T) { t.Fatalf("unable to read msg: %v", err) return false } - if !reflect.DeepEqual(msg, newMsg) { - t.Fatalf("messages don't match after re-encoding: %v "+ - "vs %v", spew.Sdump(msg), spew.Sdump(newMsg)) + if !assert.Equalf(t, msg, newMsg, "message mismatch") { return false } @@ -362,24 +360,26 @@ func TestLightningWireProtocol(t *testing.T) { return } - // 1/2 chance empty upfront shutdown script. + // 1/2 chance empty TLV records. if r.Intn(2) == 0 { req.UpfrontShutdownScript, err = randDeliveryAddress(r) if err != nil { t.Fatalf("unable to generate delivery address: %v", err) return } + + req.ChannelType = new(ChannelType) + *req.ChannelType = ChannelType(*randRawFeatureVector(r)) + + req.LeaseExpiry = new(LeaseExpiry) + *req.LeaseExpiry = LeaseExpiry(1337) } else { req.UpfrontShutdownScript = []byte{} } - // 1/2 chance how having more TLV data after the - // shutdown script. + // 1/2 chance additional TLV data. if r.Intn(2) == 0 { - // TLV type 1 of length 2. - req.ExtraData = []byte{1, 2, 0xff, 0xff} - } else { - req.ExtraData = []byte{} + req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00} } v[0] = reflect.ValueOf(req) @@ -432,23 +432,26 @@ func TestLightningWireProtocol(t *testing.T) { return } - // 1/2 chance empty upfront shutdown script. + // 1/2 chance empty TLV records. if r.Intn(2) == 0 { req.UpfrontShutdownScript, err = randDeliveryAddress(r) if err != nil { t.Fatalf("unable to generate delivery address: %v", err) return } + + req.ChannelType = new(ChannelType) + *req.ChannelType = ChannelType(*randRawFeatureVector(r)) + + req.LeaseExpiry = new(LeaseExpiry) + *req.LeaseExpiry = LeaseExpiry(1337) } else { req.UpfrontShutdownScript = []byte{} } - // 1/2 chance how having more TLV data after the - // shutdown script. + + // 1/2 chance additional TLV data. if r.Intn(2) == 0 { - // TLV type 1 of length 2. - req.ExtraData = []byte{1, 2, 0xff, 0xff} - } else { - req.ExtraData = []byte{} + req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00} } v[0] = reflect.ValueOf(req) diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 9407d0037a7..d237584167c 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/tlv" ) // FundingFlag represents the possible bit mask values for the ChannelFlags @@ -129,6 +130,16 @@ type OpenChannel struct { // and its length followed by the script will be written if it is set. UpfrontShutdownScript DeliveryAddress + // ChannelType is the explicit channel type the initiator wishes to + // open. + ChannelType *ChannelType + + // LeaseExpiry represents the absolute expiration height of a channel + // lease. This is a custom TLV record that will only apply when a leased + // channel is being opened using the script enforced lease commitment + // type. + LeaseExpiry *LeaseExpiry + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -151,11 +162,14 @@ var _ Message = (*OpenChannel)(nil) // // This is part of the lnwire.Message interface. func (o *OpenChannel) Encode(w io.Writer, pver uint32) error { - // Since the upfront script is encoded as a TLV record, concatenate it - // with the ExtraData, and write them as one. - tlvRecords, err := packShutdownScript( - o.UpfrontShutdownScript, o.ExtraData, - ) + recordProducers := []tlv.RecordProducer{&o.UpfrontShutdownScript} + if o.ChannelType != nil { + recordProducers = append(recordProducers, o.ChannelType) + } + if o.LeaseExpiry != nil { + recordProducers = append(recordProducers, o.LeaseExpiry) + } + err := EncodeMessageExtraData(&o.ExtraData, recordProducers...) if err != nil { return err } @@ -179,7 +193,7 @@ func (o *OpenChannel) Encode(w io.Writer, pver uint32) error { o.HtlcPoint, o.FirstCommitmentPoint, o.ChannelFlags, - tlvRecords, + o.ExtraData, ) } @@ -222,13 +236,29 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error { return err } - o.UpfrontShutdownScript, o.ExtraData, err = parseShutdownScript( - tlvRecords, + // Next we'll parse out the set of known records, keeping the raw tlv + // bytes untouched to ensure we don't drop any bytes erroneously. + var ( + chanType ChannelType + leaseExpiry LeaseExpiry + ) + typeMap, err := tlvRecords.ExtractRecords( + &o.UpfrontShutdownScript, &chanType, &leaseExpiry, ) if err != nil { return err } + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil { + o.ChannelType = &chanType + } + if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { + o.LeaseExpiry = &leaseExpiry + } + + o.ExtraData = tlvRecords + return nil } diff --git a/lnwire/typed_delivery_addr.go b/lnwire/typed_delivery_addr.go index 9ad53b1aeed..90a101b346b 100644 --- a/lnwire/typed_delivery_addr.go +++ b/lnwire/typed_delivery_addr.go @@ -24,11 +24,11 @@ const ( // p2wpkh. type DeliveryAddress []byte -// NewRecord returns a TLV record that can be used to encode the delivery -// address within the ExtraData TLV stream. This was intorudced in order to +// Record returns a TLV record that can be used to encode the delivery +// address within the ExtraData TLV stream. This was introduced in order to // allow the OpenChannel/AcceptChannel messages to properly be extended with // TLV types. -func (d *DeliveryAddress) NewRecord() tlv.Record { +func (d *DeliveryAddress) Record() tlv.Record { addrBytes := (*[]byte)(d) return tlv.MakeDynamicRecord( diff --git a/lnwire/typed_delivery_addr_test.go b/lnwire/typed_delivery_addr_test.go index d5d9c703a18..9d00bc8bd8f 100644 --- a/lnwire/typed_delivery_addr_test.go +++ b/lnwire/typed_delivery_addr_test.go @@ -15,13 +15,13 @@ func TestDeliveryAddressEncodeDecode(t *testing.T) { ) var extraData ExtraOpaqueData - err := extraData.PackRecords(addr.NewRecord()) + err := extraData.PackRecords(&addr) if err != nil { t.Fatal(err) } var addr2 DeliveryAddress - tlvs, err := extraData.ExtractRecords(addr2.NewRecord()) + tlvs, err := extraData.ExtractRecords(&addr2) if err != nil { t.Fatal(err) } diff --git a/lnwire/typed_lease_expiry.go b/lnwire/typed_lease_expiry.go new file mode 100644 index 00000000000..d1a5609c088 --- /dev/null +++ b/lnwire/typed_lease_expiry.go @@ -0,0 +1,52 @@ +package lnwire + +import ( + "io" + + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // LeaseExpiryType is the type of the experimental record used to + // communicate the expiration of a channel lease throughout the channel + // funding process. + // + // TODO: Decide on actual TLV type. Custom records start at 2^16. + LeaseExpiryRecordType tlv.Type = 1 << 16 +) + +// LeaseExpiry represents the absolute expiration height of a channel lease. All +// outputs that pay directly to the channel initiator are locked until this +// height is reached. +type LeaseExpiry uint32 + +// Record returns a TLV record that can be used to encode/decode the LeaseExpiry +// type from a given TLV stream. +func (l *LeaseExpiry) Record() tlv.Record { + return tlv.MakeStaticRecord( + LeaseExpiryRecordType, l, 4, leaseExpiryEncoder, leaseExpiryDecoder, + ) +} + +// leaseExpiryEncoder is a custom TLV encoder for the LeaseExpiry record. +func leaseExpiryEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*LeaseExpiry); ok { + return tlv.EUint32T(w, uint32(*v), buf) + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.LeaseExpiry") +} + +// leaseExpiryDecoder is a custom TLV decoder for the LeaseExpiry record. +func leaseExpiryDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { + if v, ok := val.(*LeaseExpiry); ok { + var leaseExpiry uint32 + if err := tlv.DUint32(r, &leaseExpiry, buf, l); err != nil { + return err + } + *v = LeaseExpiry(leaseExpiry) + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.LeaseExpiry") +} diff --git a/lnwire/typed_lease_expiry_test.go b/lnwire/typed_lease_expiry_test.go new file mode 100644 index 00000000000..d5f797b200b --- /dev/null +++ b/lnwire/typed_lease_expiry_test.go @@ -0,0 +1,25 @@ +package lnwire + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestChannelTypeEncodeDecode tests that we're able to properly encode and +// decode channel types within TLV streams. +func TestLeaseExpiryEncodeDecode(t *testing.T) { + t.Parallel() + + leaseExpiry := LeaseExpiry(1337) + + var extraData ExtraOpaqueData + require.NoError(t, extraData.PackRecords(&leaseExpiry)) + + var leaseExpiry2 LeaseExpiry + tlvs, err := extraData.ExtractRecords(&leaseExpiry2) + require.NoError(t, err) + + require.Contains(t, tlvs, LeaseExpiryRecordType) + require.Equal(t, leaseExpiry, leaseExpiry2) +} diff --git a/peer/test_utils.go b/peer/test_utils.go index e2534c1cd8b..5b5f35c6e86 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -72,6 +72,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, bobDustLimit := btcutil.Amount(1300) csvTimeoutAlice := uint32(5) csvTimeoutBob := uint32(4) + isAliceInitiator := true prevOut := &wire.OutPoint{ Hash: channels.TestHdSeed, @@ -155,6 +156,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, + isAliceInitiator, 0, ) if err != nil { return nil, nil, nil, err @@ -222,7 +224,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, FundingOutpoint: *prevOut, ShortChannelID: shortChanID, ChanType: channeldb.SingleFunderTweaklessBit, - IsInitiator: true, + IsInitiator: isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: bobCommitPoint, RevocationProducer: alicePreimageProducer, @@ -239,7 +241,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, IdentityPub: bobKeyPub, FundingOutpoint: *prevOut, ChanType: channeldb.SingleFunderTweaklessBit, - IsInitiator: false, + IsInitiator: !isAliceInitiator, Capacity: channelCapacity, RemoteCurrentRevocation: aliceCommitPoint, RevocationProducer: bobPreimageProducer, diff --git a/rpcserver.go b/rpcserver.go index 8ff36d964b4..fe9c276f218 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1913,6 +1913,41 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, err) } + var channelType *lnwire.ChannelType + switch in.CommitmentType { + case lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE: + break + + case lnrpc.CommitmentType_LEGACY: + channelType = new(lnwire.ChannelType) + *channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector()) + + case lnrpc.CommitmentType_STATIC_REMOTE_KEY: + channelType = new(lnwire.ChannelType) + *channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + )) + + case lnrpc.CommitmentType_ANCHORS: + channelType = new(lnwire.ChannelType) + *channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + )) + + case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: + channelType = new(lnwire.ChannelType) + *channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector( + lnwire.StaticRemoteKeyRequired, + lnwire.AnchorsZeroFeeHtlcTxRequired, + lnwire.ScriptEnforcedLeaseRequired, + )) + + default: + return nil, fmt.Errorf("unhandled request channel type %v", + in.CommitmentType) + } + // Instruct the server to trigger the necessary events to attempt to // open a new channel. A stream is returned in place, this stream will // be used to consume updates of the state of the pending channel. @@ -1930,6 +1965,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, MaxValueInFlight: maxValue, MaxHtlcs: maxHtlcs, MaxLocalCsv: uint16(in.MaxLocalCsv), + ChannelType: channelType, }, nil } @@ -3531,6 +3567,10 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { // Extract the commitment type from the channel type flags. We must // first check whether it has anchors, since in that case it would also // be tweakless. + if chanType.HasLeaseExpiration() { + return lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE + } + if chanType.HasAnchors() { return lnrpc.CommitmentType_ANCHORS } diff --git a/sample-lnd.conf b/sample-lnd.conf index 56fba9d672d..3fe713070b5 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1070,6 +1070,12 @@ litecoin.node=ltcd ; (Deprecates the previous "protocol.anchors" setting.) ; protocol.no-anchors=true +; Set to disable support for script enforced lease channel commitments. If not +; set, lnd will accept these channels by default if the remote channel party +; proposes them. Note that lnd will require 1 UTXO to be reserved for this +; channel type if it is enabled. +; protocol.no-script-enforced-lease=true + [db] diff --git a/server.go b/server.go index 2b914b90a4c..963ae64bc4f 100644 --- a/server.go +++ b/server.go @@ -413,10 +413,11 @@ func newServer(cfg *Config, listenAddrs []net.Addr, ) featureMgr, err := feature.NewManager(feature.Config{ - NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), - NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(), - NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), - NoWumbo: !cfg.ProtocolOptions.Wumbo(), + NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), + NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(), + NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(), + NoWumbo: !cfg.ProtocolOptions.Wumbo(), + NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(), }) if err != nil { return nil, err diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 5869e45644c..fd048428fe1 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -459,9 +459,11 @@ func (s *UtxoSweeper) SweepInput(input input.Input, return nil, err } + absoluteTimeLock, _ := input.RequiredLockTime() log.Infof("Sweep request received: out_point=%v, witness_type=%v, "+ - "time_lock=%v, amount=%v, params=(%v)", - input.OutPoint(), input.WitnessType(), input.BlocksToMaturity(), + "relative_time_lock=%v, absolute_time_lock=%v, amount=%v, "+ + "params=(%v)", input.OutPoint(), input.WitnessType(), + input.BlocksToMaturity(), absoluteTimeLock, btcutil.Amount(input.SignDesc().Output.Value), params) sweeperInput := &sweepInputMessage{ @@ -534,6 +536,17 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) { log.Debugf("Already pending input %v received", outpoint) + // Before updating the input details, check if + // an exclusive group was set, and if so, assume + // this input as finalized and remove all other + // inputs belonging to the same exclusive group. + var prevExclGroup *uint64 + if pendInput.params.ExclusiveGroup != nil && + input.params.ExclusiveGroup == nil { + prevExclGroup = new(uint64) + *prevExclGroup = *pendInput.params.ExclusiveGroup + } + // Update input details and sweep parameters. // The re-offered input details may contain a // change to the unconfirmed parent tx info. @@ -545,6 +558,11 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) { pendInput.listeners = append( pendInput.listeners, input.resultChan, ) + + if prevExclGroup != nil { + s.removeExclusiveGroup(*prevExclGroup) + } + continue }