diff --git a/config_builder.go b/config_builder.go index 4b585cb58..1c3a842ef 100644 --- a/config_builder.go +++ b/config_builder.go @@ -50,6 +50,7 @@ import ( "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sqldb" + "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower/wtclient" @@ -187,6 +188,14 @@ type AuxComponents struct { // AuxChanCloser is an optional channel closer that can be used to // modify the way a coop-close transaction is constructed. AuxChanCloser fn.Option[chancloser.AuxChanCloser] + + // AuxSweeper is an optional interface that can be used to modify the + // way sweep transaction are generated. + AuxSweeper fn.Option[sweep.AuxSweeper] + + // AuxContractResolver is an optional interface that can be used to + // modify the way contracts are resolved. + AuxContractResolver fn.Option[lnwallet.AuxContractResolver] } // DefaultWalletImpl is the default implementation of our normal, btcwallet diff --git a/contractcourt/breach_arbitrator.go b/contractcourt/breach_arbitrator.go index e3dd85ea6..dc690e85c 100644 --- a/contractcourt/breach_arbitrator.go +++ b/contractcourt/breach_arbitrator.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/labels" @@ -22,6 +23,8 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/sweep" + "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -147,7 +150,7 @@ type BreachConfig struct { Estimator chainfee.Estimator // GenSweepScript generates the receiving scripts for swept outputs. - GenSweepScript func() ([]byte, error) + GenSweepScript func() fn.Result[lnwallet.AddrWithKey] // Notifier provides a publish/subscribe interface for event driven // notifications regarding the confirmation of txids. @@ -172,6 +175,10 @@ type BreachConfig struct { // breached channels. This is used in conjunction with DB to recover // from crashes, restarts, or other failures. Store RetributionStorer + + // AuxSweeper is an optional interface that can be used to modify the + // way sweep transaction are generated. + AuxSweeper fn.Option[sweep.AuxSweeper] } // BreachArbitrator is a special subsystem which is responsible for watching and @@ -735,10 +742,28 @@ justiceTxBroadcast: brarLog.Debugf("Broadcasting justice tx: %v", lnutils.SpewLogClosure( finalTx)) + // As we're about to broadcast our breach transaction, we'll notify the + // aux sweeper of our broadcast attempt first. + err = fn.MapOptionZ(b.cfg.AuxSweeper, func(aux sweep.AuxSweeper) error { + bumpReq := sweep.BumpRequest{ + Inputs: finalTx.inputs, + DeliveryAddress: finalTx.sweepAddr, + ExtraTxOut: finalTx.extraTxOut, + } + + return aux.NotifyBroadcast( + &bumpReq, finalTx.justiceTx, finalTx.fee, + ) + }) + if err != nil { + brarLog.Errorf("unable to notify broadcast: %w", err) + return + } + // We'll now attempt to broadcast the transaction which finalized the // channel's retribution against the cheating counter party. label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil) - err = b.cfg.PublishTransaction(finalTx, label) + err = b.cfg.PublishTransaction(finalTx.justiceTx, label) if err != nil { brarLog.Errorf("Unable to broadcast justice tx: %v", err) } @@ -858,7 +883,9 @@ Loop: "spending commitment outs: %v", lnutils.SpewLogClosure(tx)) - err = b.cfg.PublishTransaction(tx, label) + err = b.cfg.PublishTransaction( + tx.justiceTx, label, + ) if err != nil { brarLog.Warnf("Unable to broadcast "+ "commit out spending justice "+ @@ -873,7 +900,9 @@ Loop: "spending HTLC outs: %v", lnutils.SpewLogClosure(tx)) - err = b.cfg.PublishTransaction(tx, label) + err = b.cfg.PublishTransaction( + tx.justiceTx, label, + ) if err != nil { brarLog.Warnf("Unable to broadcast "+ "HTLC out spending justice "+ @@ -888,7 +917,9 @@ Loop: "spending second-level HTLC output: %v", lnutils.SpewLogClosure(tx)) - err = b.cfg.PublishTransaction(tx, label) + err = b.cfg.PublishTransaction( + tx.justiceTx, label, + ) if err != nil { brarLog.Warnf("Unable to broadcast "+ "second-level HTLC out "+ @@ -1067,15 +1098,18 @@ type breachedOutput struct { secondLevelTapTweak [32]byte witnessFunc input.WitnessGenerator + + resolutionBlob fn.Option[tlv.Blob] + + // TODO(roasbeef): function opt and hook into brar } // makeBreachedOutput assembles a new breachedOutput that can be used by the // breach arbiter to construct a justice or sweep transaction. func makeBreachedOutput(outpoint *wire.OutPoint, - witnessType input.StandardWitnessType, - secondLevelScript []byte, - signDescriptor *input.SignDescriptor, - confHeight uint32) breachedOutput { + witnessType input.StandardWitnessType, secondLevelScript []byte, + signDescriptor *input.SignDescriptor, confHeight uint32, + resolutionBlob fn.Option[tlv.Blob]) breachedOutput { amount := signDescriptor.Output.Value @@ -1086,6 +1120,7 @@ func makeBreachedOutput(outpoint *wire.OutPoint, witnessType: witnessType, signDesc: *signDescriptor, confHeight: confHeight, + resolutionBlob: resolutionBlob, } } @@ -1174,6 +1209,12 @@ func (bo *breachedOutput) UnconfParent() *input.TxInfo { return nil } +// ResolutionBlob returns a special opaque blob to be used to sweep/resolve this +// input. +func (bo *breachedOutput) ResolutionBlob() fn.Option[tlv.Blob] { + return bo.resolutionBlob +} + // Add compile-time constraint ensuring breachedOutput implements the Input // interface. var _ input.Input = (*breachedOutput)(nil) @@ -1258,6 +1299,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, nil, breachInfo.LocalOutputSignDesc, breachInfo.BreachHeight, + breachInfo.LocalResolutionBlob, ) breachedOutputs = append(breachedOutputs, localOutput) @@ -1284,6 +1326,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, nil, breachInfo.RemoteOutputSignDesc, breachInfo.BreachHeight, + breachInfo.RemoteResolutionBlob, ) breachedOutputs = append(breachedOutputs, remoteOutput) @@ -1318,6 +1361,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, breachInfo.HtlcRetributions[i].SecondLevelWitnessScript, &breachInfo.HtlcRetributions[i].SignDesc, breachInfo.BreachHeight, + breachInfo.HtlcRetributions[i].ResolutionBlob, ) // For taproot outputs, we also need to hold onto the second @@ -1357,10 +1401,10 @@ func newRetributionInfo(chanPoint *wire.OutPoint, // spend the to_local output and commitment level HTLC outputs separately, // before the CSV locks expire. type justiceTxVariants struct { - spendAll *wire.MsgTx - spendCommitOuts *wire.MsgTx - spendHTLCs *wire.MsgTx - spendSecondLevelHTLCs []*wire.MsgTx + spendAll *justiceTxCtx + spendCommitOuts *justiceTxCtx + spendHTLCs *justiceTxCtx + spendSecondLevelHTLCs []*justiceTxCtx } // createJusticeTx creates transactions which exacts "justice" by sweeping ALL @@ -1424,7 +1468,9 @@ func (b *BreachArbitrator) createJusticeTx( err) } - secondLevelSweeps := make([]*wire.MsgTx, 0, len(secondLevelInputs)) + // TODO(roasbeef): only register one of them? + + secondLevelSweeps := make([]*justiceTxCtx, 0, len(secondLevelInputs)) for _, input := range secondLevelInputs { sweepTx, err := b.createSweepTx(input) if err != nil { @@ -1441,9 +1487,23 @@ func (b *BreachArbitrator) createJusticeTx( return txs, nil } +// justiceTxCtx contains the justice transaction along with other related meta +// data. +type justiceTxCtx struct { + justiceTx *wire.MsgTx + + sweepAddr lnwallet.AddrWithKey + + extraTxOut fn.Option[sweep.SweepOutput] + + fee btcutil.Amount + + inputs []input.Input +} + // createSweepTx creates a tx that sweeps the passed inputs back to our wallet. -func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx, - error) { +func (b *BreachArbitrator) createSweepTx( + inputs ...input.Input) (*justiceTxCtx, error) { if len(inputs) == 0 { return nil, nil @@ -1466,6 +1526,18 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx, // nLockTime, and output are already included in the TxWeightEstimator. weightEstimate.AddP2TROutput() + // If any of our inputs has a resolution blob, then we'll add another + // P2TR _output_, since we'll want to separate the custom channel + // outputs from the regular, BTC only outputs. So we only need one such + // output, which'll carry the custom channel "valuables" from both the + // breached commitment and HTLC outputs. + hasBlobs := fn.Any(func(i input.Input) bool { + return i.ResolutionBlob().IsSome() + }, inputs) + if hasBlobs { + weightEstimate.AddP2TROutput() + } + // Next, we iterate over the breached outputs contained in the // retribution info. For each, we switch over the witness type such // that we contribute the appropriate weight for each input and @@ -1499,13 +1571,13 @@ func (b *BreachArbitrator) createSweepTx(inputs ...input.Input) (*wire.MsgTx, // sweepSpendableOutputsTxn creates a signed transaction from a sequence of // spendable outputs by sweeping the funds into a single p2wkh output. func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit, - inputs ...input.Input) (*wire.MsgTx, error) { + inputs ...input.Input) (*justiceTxCtx, error) { // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. // TODO(roasbeef): possibly create many outputs to minimize change in // the future? - pkScript, err := b.cfg.GenSweepScript() + pkScript, err := b.cfg.GenSweepScript().Unpack() if err != nil { return nil, err } @@ -1524,6 +1596,18 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit, } txFee := feePerKw.FeeForWeight(txWeight) + // At this point, we'll check to see if we have any extra outputs to + // add from the aux sweeper. + extraChangeOut := fn.MapOptionZ( + b.cfg.AuxSweeper, + func(aux sweep.AuxSweeper) fn.Result[sweep.SweepOutput] { + return aux.DeriveSweepAddr(inputs, pkScript) + }, + ) + if err := extraChangeOut.Err(); err != nil { + return nil, err + } + // TODO(roasbeef): already start to siphon their funds into fees sweepAmt := int64(totalAmt - txFee) @@ -1531,12 +1615,24 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit, // information gathered above and the provided retribution information. txn := wire.NewMsgTx(2) - // We begin by adding the output to which our funds will be deposited. + // First, we'll add the extra sweep output if it exists, subtracting the + // amount from the sweep amt. + if b.cfg.AuxSweeper.IsSome() { + extraChangeOut.WhenResult(func(o sweep.SweepOutput) { + sweepAmt -= o.Value + + txn.AddTxOut(&o.TxOut) + }) + } + + // Next, we'll add the output to which our funds will be deposited. txn.AddTxOut(&wire.TxOut{ - PkScript: pkScript, + PkScript: pkScript.DeliveryAddress, Value: sweepAmt, }) + // TODO(roasbeef): add other output change modify sweep amt + // Next, we add all of the spendable outputs as inputs to the // transaction. for _, inp := range inputs { @@ -1592,7 +1688,13 @@ func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit, } } - return txn, nil + return &justiceTxCtx{ + justiceTx: txn, + sweepAddr: pkScript, + extraTxOut: extraChangeOut.Option(), + fee: txFee, + inputs: inputs, + }, nil } // RetributionStore handles persistence of retribution states to disk and is @@ -1622,13 +1724,29 @@ func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase { // commitment, we'll need to stash the control block. case input.TaprootRemoteCommitSpend: //nolint:lll - tapCase.CtrlBlocks.CommitSweepCtrlBlock = bo.signDesc.ControlBlock + tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = bo.signDesc.ControlBlock + + bo.resolutionBlob.WhenSome(func(blob tlv.Blob) { + tapCase.SettledCommitBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType2]( + blob, + ), + ) + }) // To spend the revoked output again, we'll store the same // control block value as above, but in a different place. case input.TaprootCommitmentRevoke: //nolint:lll - tapCase.CtrlBlocks.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock + tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock + + bo.resolutionBlob.WhenSome(func(blob tlv.Blob) { + tapCase.BreachedCommitBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType3]( + blob, + ), + ) + }) // For spending the HTLC outputs, we'll store the first and // second level tweak values. @@ -1642,10 +1760,10 @@ func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase { secondLevelTweak := bo.secondLevelTapTweak //nolint:lll - tapCase.TapTweaks.BreachedHtlcTweaks[resID] = firstLevelTweak + tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID] = firstLevelTweak //nolint:lll - tapCase.TapTweaks.BreachedSecondLevelHltcTweaks[resID] = secondLevelTweak + tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID] = secondLevelTweak } } @@ -1665,13 +1783,25 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase, // commitment, we'll apply the control block. case input.TaprootRemoteCommitSpend: //nolint:lll - bo.signDesc.ControlBlock = tapCase.CtrlBlocks.CommitSweepCtrlBlock + bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock + + tapCase.SettledCommitBlob.WhenSomeV( + func(blob tlv.Blob) { + bo.resolutionBlob = fn.Some(blob) + }, + ) // To spend the revoked output again, we'll apply the same // control block value as above, but to a different place. case input.TaprootCommitmentRevoke: //nolint:lll - bo.signDesc.ControlBlock = tapCase.CtrlBlocks.RevokeSweepCtrlBlock + bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock + + tapCase.BreachedCommitBlob.WhenSomeV( + func(blob tlv.Blob) { + bo.resolutionBlob = fn.Some(blob) + }, + ) // For spending the HTLC outputs, we'll apply the first and // second level tweak values. @@ -1680,7 +1810,8 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase, case input.TaprootHtlcOfferedRevoke: resID := newResolverID(bo.OutPoint()) - tap1, ok := tapCase.TapTweaks.BreachedHtlcTweaks[resID] + //nolint:lll + tap1, ok := tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID] if !ok { return fmt.Errorf("unable to find taproot "+ "tweak for: %v", bo.OutPoint()) @@ -1688,7 +1819,7 @@ func applyTaprootRetInfo(tapCase *taprootBriefcase, bo.signDesc.TapTweak = tap1[:] //nolint:lll - tap2, ok := tapCase.TapTweaks.BreachedSecondLevelHltcTweaks[resID] + tap2, ok := tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID] if !ok { return fmt.Errorf("unable to find taproot "+ "tweak for: %v", bo.OutPoint()) diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go index cf575f153..bd4ad8568 100644 --- a/contractcourt/breach_arbitrator_test.go +++ b/contractcourt/breach_arbitrator_test.go @@ -1199,6 +1199,8 @@ func TestBreachCreateJusticeTx(t *testing.T) { input.HtlcSecondLevelRevoke, } + rBlob := fn.Some([]byte{0x01}) + breachedOutputs := make([]breachedOutput, len(outputTypes)) for i, wt := range outputTypes { // Create a fake breached output for each type, ensuring they @@ -1217,6 +1219,7 @@ func TestBreachCreateJusticeTx(t *testing.T) { nil, signDesc, 1, + rBlob, ) } @@ -1227,16 +1230,16 @@ func TestBreachCreateJusticeTx(t *testing.T) { // The spendAll tx should be spending all the outputs. This is the // "regular" justice transaction type. - require.Len(t, justiceTxs.spendAll.TxIn, len(breachedOutputs)) + require.Len(t, justiceTxs.spendAll.justiceTx.TxIn, len(breachedOutputs)) // The spendCommitOuts tx should be spending the 4 types of commit outs // (note that in practice there will be at most two commit outputs per // commit, but we test all 4 types here). - require.Len(t, justiceTxs.spendCommitOuts.TxIn, 4) + require.Len(t, justiceTxs.spendCommitOuts.justiceTx.TxIn, 4) // Check that the spendHTLCs tx is spending the two revoked commitment // level HTLC output types. - require.Len(t, justiceTxs.spendHTLCs.TxIn, 2) + require.Len(t, justiceTxs.spendHTLCs.justiceTx.TxIn, 2) // Finally, check that the spendSecondLevelHTLCs txs are spending the // second level type. @@ -1592,6 +1595,9 @@ func testBreachSpends(t *testing.T, test breachTest) { retribution, err := lnwallet.NewBreachRetribution( alice.State(), height, 1, forceCloseTx, fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}), + fn.Some[lnwallet.AuxContractResolver]( + &lnwallet.MockAuxContractResolver{}, + ), ) require.NoError(t, err, "unable to create breach retribution") @@ -1802,6 +1808,9 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { retribution, err := lnwallet.NewBreachRetribution( alice.State(), height, uint32(blockHeight), forceCloseTx, fn.Some[lnwallet.AuxLeafStore](&lnwallet.MockAuxLeafStore{}), + fn.Some[lnwallet.AuxContractResolver]( + &lnwallet.MockAuxContractResolver{}, + ), ) require.NoError(t, err, "unable to create breach retribution") @@ -2129,15 +2138,19 @@ func createTestArbiter(t *testing.T, contractBreaches chan *ContractBreachEvent, // Assemble our test arbiter. notifier := mock.MakeMockSpendNotifier() ba := NewBreachArbitrator(&BreachConfig{ - CloseLink: func(_ *wire.OutPoint, _ ChannelCloseType) {}, - DB: db.ChannelStateDB(), - Estimator: chainfee.NewStaticEstimator(12500, 0), - GenSweepScript: func() ([]byte, error) { return nil, nil }, - ContractBreaches: contractBreaches, - Signer: signer, - Notifier: notifier, - PublishTransaction: func(_ *wire.MsgTx, _ string) error { return nil }, - Store: store, + CloseLink: func(_ *wire.OutPoint, _ ChannelCloseType) {}, + DB: db.ChannelStateDB(), + Estimator: chainfee.NewStaticEstimator(12500, 0), + GenSweepScript: func() fn.Result[lnwallet.AddrWithKey] { + return fn.Ok(lnwallet.AddrWithKey{}) + }, + ContractBreaches: contractBreaches, + Signer: signer, + Notifier: notifier, + PublishTransaction: func(_ *wire.MsgTx, _ string) error { + return nil + }, + Store: store, }) if err := ba.Start(); err != nil { diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go index 95f6a933d..26df50b30 100644 --- a/contractcourt/briefcase.go +++ b/contractcourt/briefcase.go @@ -10,9 +10,11 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/tlv" ) // ContractResolutions is a wrapper struct around the two forms of resolutions @@ -1553,7 +1555,13 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { commitResolution := c.CommitResolution commitSignDesc := commitResolution.SelfOutputSignDesc //nolint:lll - tapCase.CtrlBlocks.CommitSweepCtrlBlock = commitSignDesc.ControlBlock + tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = commitSignDesc.ControlBlock + + c.CommitResolution.ResolutionBlob.WhenSome(func(b []byte) { + tapCase.SettledCommitBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType2](b), + ) + }) } for _, htlc := range c.HtlcResolutions.IncomingHTLCs { @@ -1571,7 +1579,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { htlc.SignedSuccessTx.TxIn[0].PreviousOutPoint, ) //nolint:lll - tapCase.CtrlBlocks.SecondLevelCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] = ctrlBlock // For HTLCs we need to go to the second level for, we // also need to store the control block needed to @@ -1580,12 +1588,12 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { //nolint:lll bridgeCtrlBlock := htlc.SignDetails.SignDesc.ControlBlock //nolint:lll - tapCase.CtrlBlocks.IncomingHtlcCtrlBlocks[resID] = bridgeCtrlBlock + tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = bridgeCtrlBlock } } else { resID := newResolverID(htlc.ClaimOutpoint) //nolint:lll - tapCase.CtrlBlocks.IncomingHtlcCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = ctrlBlock } } for _, htlc := range c.HtlcResolutions.OutgoingHTLCs { @@ -1603,7 +1611,7 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { htlc.SignedTimeoutTx.TxIn[0].PreviousOutPoint, ) //nolint:lll - tapCase.CtrlBlocks.SecondLevelCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] = ctrlBlock // For HTLCs we need to go to the second level for, we // also need to store the control block needed to @@ -1614,18 +1622,18 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { //nolint:lll bridgeCtrlBlock := htlc.SignDetails.SignDesc.ControlBlock //nolint:lll - tapCase.CtrlBlocks.OutgoingHtlcCtrlBlocks[resID] = bridgeCtrlBlock + tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = bridgeCtrlBlock } } else { resID := newResolverID(htlc.ClaimOutpoint) //nolint:lll - tapCase.CtrlBlocks.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock } } if c.AnchorResolution != nil { anchorSignDesc := c.AnchorResolution.AnchorSignDescriptor - tapCase.TapTweaks.AnchorTweak = anchorSignDesc.TapTweak + tapCase.TapTweaks.Val.AnchorTweak = anchorSignDesc.TapTweak } return tapCase.Encode(w) @@ -1639,7 +1647,11 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error { if c.CommitResolution != nil { c.CommitResolution.SelfOutputSignDesc.ControlBlock = - tapCase.CtrlBlocks.CommitSweepCtrlBlock + tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock + + tapCase.SettledCommitBlob.WhenSomeV(func(b []byte) { + c.CommitResolution.ResolutionBlob = fn.Some(b) + }) } for i := range c.HtlcResolutions.IncomingHTLCs { @@ -1652,19 +1664,19 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error { ) //nolint:lll - ctrlBlock := tapCase.CtrlBlocks.SecondLevelCtrlBlocks[resID] + ctrlBlock := tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] htlc.SweepSignDesc.ControlBlock = ctrlBlock //nolint:lll if htlc.SignDetails != nil { - bridgeCtrlBlock := tapCase.CtrlBlocks.IncomingHtlcCtrlBlocks[resID] + bridgeCtrlBlock := tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] htlc.SignDetails.SignDesc.ControlBlock = bridgeCtrlBlock } } else { resID = newResolverID(htlc.ClaimOutpoint) //nolint:lll - ctrlBlock := tapCase.CtrlBlocks.IncomingHtlcCtrlBlocks[resID] + ctrlBlock := tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] htlc.SweepSignDesc.ControlBlock = ctrlBlock } @@ -1680,19 +1692,19 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error { ) //nolint:lll - ctrlBlock := tapCase.CtrlBlocks.SecondLevelCtrlBlocks[resID] + ctrlBlock := tapCase.CtrlBlocks.Val.SecondLevelCtrlBlocks[resID] htlc.SweepSignDesc.ControlBlock = ctrlBlock //nolint:lll if htlc.SignDetails != nil { - bridgeCtrlBlock := tapCase.CtrlBlocks.OutgoingHtlcCtrlBlocks[resID] + bridgeCtrlBlock := tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] htlc.SignDetails.SignDesc.ControlBlock = bridgeCtrlBlock } } else { resID = newResolverID(htlc.ClaimOutpoint) //nolint:lll - ctrlBlock := tapCase.CtrlBlocks.OutgoingHtlcCtrlBlocks[resID] + ctrlBlock := tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] htlc.SweepSignDesc.ControlBlock = ctrlBlock } @@ -1701,7 +1713,7 @@ func decodeTapRootAuxData(r io.Reader, c *ContractResolutions) error { if c.AnchorResolution != nil { c.AnchorResolution.AnchorSignDescriptor.TapTweak = - tapCase.TapTweaks.AnchorTweak + tapCase.TapTweaks.Val.AnchorTweak } return nil diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index d61e47901..c29178b43 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -225,6 +225,10 @@ type ChainArbitratorConfig struct { // AuxSigner is an optional signer that can be used to sign auxiliary // leaves for certain custom channel types. AuxSigner fn.Option[lnwallet.AuxSigner] + + // AuxResolver is an optional interface that can be used to modify the + // way contracts are resolved. + AuxResolver fn.Option[lnwallet.AuxContractResolver] } // ChainArbitrator is a sub-system that oversees the on-chain resolution of all @@ -314,6 +318,9 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) { chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s)) }) + a.c.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) { + chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s)) + }) chanMachine, err := lnwallet.NewLightningChannel( a.c.cfg.Signer, channel, nil, chanOpts..., @@ -367,6 +374,9 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) a.c.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) { chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s)) }) + a.c.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) { + chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s)) + }) // Finally, we'll force close the channel completing // the force close workflow. @@ -581,6 +591,8 @@ func (c *ChainArbitrator) Start() error { isOurAddr: c.cfg.IsOurAddress, contractBreach: breachClosure, extractStateNumHint: lnwallet.GetStateNumHint, + auxLeafStore: c.cfg.AuxLeafStore, + auxResolver: c.cfg.AuxResolver, }, ) if err != nil { @@ -1210,6 +1222,8 @@ func (c *ChainArbitrator) WatchNewChannel(newChan *channeldb.OpenChannel) error ) }, extractStateNumHint: lnwallet.GetStateNumHint, + auxLeafStore: c.cfg.AuxLeafStore, + auxResolver: c.cfg.AuxResolver, }, ) if err != nil { diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index b1e3fc1c2..ef4a4e200 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -196,6 +196,9 @@ type chainWatcherConfig struct { // auxLeafStore can be used to fetch information for custom channels. auxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // auxResolver is used to supplement contract resolution. + auxResolver fn.Option[lnwallet.AuxContractResolver] } // chainWatcher is a system that's assigned to every active channel. The duty @@ -896,7 +899,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, spendHeight := uint32(commitSpend.SpendingHeight) retribution, err := lnwallet.NewBreachRetribution( c.cfg.chanState, broadcastStateNum, spendHeight, - commitSpend.SpendingTx, c.cfg.auxLeafStore, + commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver, ) switch { @@ -1147,7 +1150,7 @@ func (c *chainWatcher) dispatchLocalForceClose( forceClose, err := lnwallet.NewLocalForceCloseSummary( c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum, - c.cfg.auxLeafStore, + c.cfg.auxLeafStore, c.cfg.auxResolver, ) if err != nil { return err @@ -1239,8 +1242,8 @@ func (c *chainWatcher) dispatchRemoteForceClose( // materials required to let each subscriber sweep the funds in the // channel on-chain. uniClose, err := lnwallet.NewUnilateralCloseSummary( - c.cfg.chanState, c.cfg.signer, commitSpend, - remoteCommit, commitPoint, c.cfg.auxLeafStore, + c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit, + commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver, ) if err != nil { return err diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index f2392192e..8f1169580 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -345,12 +345,18 @@ func (c *commitSweepResolver) Resolve(_ bool) (ContractResolver, error) { &c.commitResolution.SelfOutputSignDesc, c.broadcastHeight, c.commitResolution.MaturityDelay, c.leaseExpiry, + input.WithResolutionBlob( + c.commitResolution.ResolutionBlob, + ), ) } else { inp = input.NewCsvInput( &c.commitResolution.SelfOutPoint, witnessType, &c.commitResolution.SelfOutputSignDesc, c.broadcastHeight, c.commitResolution.MaturityDelay, + input.WithResolutionBlob( + c.commitResolution.ResolutionBlob, + ), ) } diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 6bda4e398..9ffa79943 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -99,6 +99,17 @@ func (h *htlcIncomingContestResolver) Resolve( return nil, nil } + // If the HTLC has custom records, then for now we'll pause resolution. + // + // TODO(roasbeef): Implement resolving HTLCs with custom records + // (follow-up PR). + if len(h.htlc.CustomRecords) != 0 { + select { //nolint:gosimple + case <-h.quit: + return nil, errResolverShuttingDown + } + } + // First try to parse the payload. If that fails, we can stop resolution // now. payload, nextHopOnionBlob, err := h.decodePayload() diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index 2466544c9..c75b89822 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -58,6 +58,17 @@ func (h *htlcOutgoingContestResolver) Resolve( return nil, nil } + // If the HTLC has custom records, then for now we'll pause resolution. + // + // TODO(roasbeef): Implement resolving HTLCs with custom records + // (follow-up PR). + if len(h.htlc.CustomRecords) != 0 { + select { //nolint:gosimple + case <-h.quit: + return nil, errResolverShuttingDown + } + } + // Otherwise, we'll watch for two external signals to decide if we'll // morph into another resolver, or fully resolve the contract. // diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 6eee939ea..3b07828d4 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -123,6 +123,17 @@ func (h *htlcSuccessResolver) Resolve( return nil, nil } + // If the HTLC has custom records, then for now we'll pause resolution. + // + // TODO(roasbeef): Implement resolving HTLCs with custom records + // (follow-up PR). + if len(h.htlc.CustomRecords) != 0 { + select { //nolint:gosimple + case <-h.quit: + return nil, errResolverShuttingDown + } + } + // If we don't have a success transaction, then this means that this is // an output on the remote party's commitment transaction. if h.htlcResolution.SignedSuccessTx == nil { diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 3227865d1..670da607d 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -426,6 +426,17 @@ func (h *htlcTimeoutResolver) Resolve( return nil, nil } + // If the HTLC has custom records, then for now we'll pause resolution. + // + // TODO(roasbeef): Implement resolving HTLCs with custom records + // (follow-up PR). + if len(h.htlc.CustomRecords) != 0 { + select { //nolint:gosimple + case <-h.quit: + return nil, errResolverShuttingDown + } + } + // Start by spending the HTLC output, either by broadcasting the // second-level timeout transaction, or directly if this is the remote // commitment. diff --git a/contractcourt/taproot_briefcase.go b/contractcourt/taproot_briefcase.go index 5931a4556..0a2beeff7 100644 --- a/contractcourt/taproot_briefcase.go +++ b/contractcourt/taproot_briefcase.go @@ -8,9 +8,6 @@ import ( ) const ( - taprootCtrlBlockType tlv.Type = 0 - taprootTapTweakType tlv.Type = 1 - commitCtrlBlockType tlv.Type = 0 revokeCtrlBlockType tlv.Type = 1 outgoingHtlcCtrlBlockType tlv.Type = 2 @@ -26,36 +23,62 @@ const ( // information we need to sweep taproot outputs. type taprootBriefcase struct { // CtrlBlock is the set of control block for the taproot outputs. - CtrlBlocks *ctrlBlocks + CtrlBlocks tlv.RecordT[tlv.TlvType0, ctrlBlocks] // TapTweaks is the set of taproot tweaks for the taproot outputs that // are to be spent via a keyspend path. This includes anchors, and any // revocation paths. - TapTweaks *tapTweaks + TapTweaks tlv.RecordT[tlv.TlvType1, tapTweaks] + + // SettledCommitBlob is an optional record that contains an opaque blob + // that may be used to properly sweep commitment outputs on a force + // close transaction. + SettledCommitBlob tlv.OptionalRecordT[tlv.TlvType2, tlv.Blob] + + // BreachCommitBlob is an optional record that contains an opaque blob + // used to sweep a remote party's breached output. + BreachedCommitBlob tlv.OptionalRecordT[tlv.TlvType3, tlv.Blob] + + // TODO(roasbeef): htlc blobs } +// TODO(roasbeef): morph into new tlv record + // newTaprootBriefcase returns a new instance of the taproot specific briefcase // variant. func newTaprootBriefcase() *taprootBriefcase { return &taprootBriefcase{ - CtrlBlocks: newCtrlBlocks(), - TapTweaks: newTapTweaks(), + CtrlBlocks: tlv.NewRecordT[tlv.TlvType0](newCtrlBlocks()), + TapTweaks: tlv.NewRecordT[tlv.TlvType1](newTapTweaks()), } } // EncodeRecords returns a slice of TLV records that should be encoded. func (t *taprootBriefcase) EncodeRecords() []tlv.Record { - return []tlv.Record{ - newCtrlBlocksRecord(&t.CtrlBlocks), - newTapTweaksRecord(&t.TapTweaks), + records := []tlv.Record{ + t.CtrlBlocks.Record(), + t.TapTweaks.Record(), } + + t.SettledCommitBlob.WhenSome( + func(r tlv.RecordT[tlv.TlvType2, tlv.Blob]) { + records = append(records, r.Record()) + }, + ) + t.BreachedCommitBlob.WhenSome( + func(r tlv.RecordT[tlv.TlvType3, tlv.Blob]) { + records = append(records, r.Record()) + }, + ) + + return records } // DecodeRecords returns a slice of TLV records that should be decoded. func (t *taprootBriefcase) DecodeRecords() []tlv.Record { return []tlv.Record{ - newCtrlBlocksRecord(&t.CtrlBlocks), - newTapTweaksRecord(&t.TapTweaks), + t.CtrlBlocks.Record(), + t.TapTweaks.Record(), } } @@ -71,12 +94,31 @@ func (t *taprootBriefcase) Encode(w io.Writer) error { // Decode decodes the given reader into the target struct. func (t *taprootBriefcase) Decode(r io.Reader) error { - stream, err := tlv.NewStream(t.DecodeRecords()...) + settledCommitBlob := t.SettledCommitBlob.Zero() + breachedCommitBlob := t.BreachedCommitBlob.Zero() + records := append( + t.DecodeRecords(), + settledCommitBlob.Record(), + breachedCommitBlob.Record(), + ) + stream, err := tlv.NewStream(records...) if err != nil { return err } - return stream.Decode(r) + typeMap, err := stream.DecodeWithParsedTypes(r) + if err != nil { + return err + } + + if val, ok := typeMap[t.SettledCommitBlob.TlvType()]; ok && val == nil { + t.SettledCommitBlob = tlv.SomeRecordT(settledCommitBlob) + } + if v, ok := typeMap[t.BreachedCommitBlob.TlvType()]; ok && v == nil { + t.BreachedCommitBlob = tlv.SomeRecordT(breachedCommitBlob) + } + + return nil } // resolverCtrlBlocks is a map of resolver IDs to their corresponding control @@ -216,8 +258,8 @@ type ctrlBlocks struct { } // newCtrlBlocks returns a new instance of the ctrlBlocks struct. -func newCtrlBlocks() *ctrlBlocks { - return &ctrlBlocks{ +func newCtrlBlocks() ctrlBlocks { + return ctrlBlocks{ OutgoingHtlcCtrlBlocks: newResolverCtrlBlocks(), IncomingHtlcCtrlBlocks: newResolverCtrlBlocks(), SecondLevelCtrlBlocks: newResolverCtrlBlocks(), @@ -260,7 +302,7 @@ func varBytesDecoder(r io.Reader, val any, buf *[8]byte, l uint64) error { // ctrlBlockEncoder is a custom TLV encoder for the ctrlBlocks struct. func ctrlBlockEncoder(w io.Writer, val any, _ *[8]byte) error { - if t, ok := val.(**ctrlBlocks); ok { + if t, ok := val.(*ctrlBlocks); ok { return (*t).Encode(w) } @@ -269,7 +311,7 @@ func ctrlBlockEncoder(w io.Writer, val any, _ *[8]byte) error { // ctrlBlockDecoder is a custom TLV decoder for the ctrlBlocks struct. func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { - if typ, ok := val.(**ctrlBlocks); ok { + if typ, ok := val.(*ctrlBlocks); ok { ctrlReader := io.LimitReader(r, int64(l)) var ctrlBlocks ctrlBlocks @@ -278,7 +320,7 @@ func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return err } - *typ = &ctrlBlocks + *typ = ctrlBlocks return nil } @@ -286,28 +328,6 @@ func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return tlv.NewTypeForDecodingErr(val, "ctrlBlocks", l, l) } -// newCtrlBlocksRecord returns a new TLV record that can be used to -// encode/decode the set of cotrol blocks for the taproot outputs for a -// channel. -func newCtrlBlocksRecord(blks **ctrlBlocks) tlv.Record { - recordSize := func() uint64 { - var ( - b bytes.Buffer - buf [8]byte - ) - if err := ctrlBlockEncoder(&b, blks, &buf); err != nil { - panic(err) - } - - return uint64(len(b.Bytes())) - } - - return tlv.MakeDynamicRecord( - taprootCtrlBlockType, blks, recordSize, ctrlBlockEncoder, - ctrlBlockDecoder, - ) -} - // EncodeRecords returns the set of TLV records that encode the control block // for the commitment transaction. func (c *ctrlBlocks) EncodeRecords() []tlv.Record { @@ -382,7 +402,21 @@ func (c *ctrlBlocks) DecodeRecords() []tlv.Record { // Record returns a TLV record that can be used to encode/decode the control // blocks. type from a given TLV stream. func (c *ctrlBlocks) Record() tlv.Record { - return tlv.MakePrimitiveRecord(commitCtrlBlockType, c) + recordSize := func() uint64 { + var ( + b bytes.Buffer + buf [8]byte + ) + if err := ctrlBlockEncoder(&b, c, &buf); err != nil { + panic(err) + } + + return uint64(len(b.Bytes())) + } + + return tlv.MakeDynamicRecord( + 0, c, recordSize, ctrlBlockEncoder, ctrlBlockDecoder, + ) } // Encode encodes the set of control blocks. @@ -530,8 +564,8 @@ type tapTweaks struct { } // newTapTweaks returns a new tapTweaks struct. -func newTapTweaks() *tapTweaks { - return &tapTweaks{ +func newTapTweaks() tapTweaks { + return tapTweaks{ BreachedHtlcTweaks: make(htlcTapTweaks), BreachedSecondLevelHltcTweaks: make(htlcTapTweaks), } @@ -539,7 +573,7 @@ func newTapTweaks() *tapTweaks { // tapTweaksEncoder is a custom TLV encoder for the tapTweaks struct. func tapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error { - if t, ok := val.(**tapTweaks); ok { + if t, ok := val.(*tapTweaks); ok { return (*t).Encode(w) } @@ -548,7 +582,7 @@ func tapTweaksEncoder(w io.Writer, val any, _ *[8]byte) error { // tapTweaksDecoder is a custom TLV decoder for the tapTweaks struct. func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { - if typ, ok := val.(**tapTweaks); ok { + if typ, ok := val.(*tapTweaks); ok { tweakReader := io.LimitReader(r, int64(l)) var tapTweaks tapTweaks @@ -557,7 +591,7 @@ func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return err } - *typ = &tapTweaks + *typ = tapTweaks return nil } @@ -565,27 +599,6 @@ func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return tlv.NewTypeForDecodingErr(val, "tapTweaks", l, l) } -// newTapTweaksRecord returns a new TLV record that can be used to -// encode/decode the tap tweak structs. -func newTapTweaksRecord(tweaks **tapTweaks) tlv.Record { - recordSize := func() uint64 { - var ( - b bytes.Buffer - buf [8]byte - ) - if err := tapTweaksEncoder(&b, tweaks, &buf); err != nil { - panic(err) - } - - return uint64(len(b.Bytes())) - } - - return tlv.MakeDynamicRecord( - taprootTapTweakType, tweaks, recordSize, tapTweaksEncoder, - tapTweaksDecoder, - ) -} - // EncodeRecords returns the set of TLV records that encode the tweaks. func (t *tapTweaks) EncodeRecords() []tlv.Record { var records []tlv.Record @@ -637,7 +650,21 @@ func (t *tapTweaks) DecodeRecords() []tlv.Record { // Record returns a TLV record that can be used to encode/decode the tap // tweaks. func (t *tapTweaks) Record() tlv.Record { - return tlv.MakePrimitiveRecord(taprootTapTweakType, t) + recordSize := func() uint64 { + var ( + b bytes.Buffer + buf [8]byte + ) + if err := tapTweaksEncoder(&b, t, &buf); err != nil { + panic(err) + } + + return uint64(len(b.Bytes())) + } + + return tlv.MakeDynamicRecord( + 0, t, recordSize, tapTweaksEncoder, tapTweaksDecoder, + ) } // Encode encodes the set of tap tweaks. diff --git a/contractcourt/taproot_briefcase_test.go b/contractcourt/taproot_briefcase_test.go index 38471ed74..a7d52d963 100644 --- a/contractcourt/taproot_briefcase_test.go +++ b/contractcourt/taproot_briefcase_test.go @@ -5,6 +5,7 @@ import ( "math/rand" "testing" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -69,19 +70,29 @@ func TestTaprootBriefcase(t *testing.T) { _, err = rand.Read(anchorTweak[:]) require.NoError(t, err) + var commitBlob [100]byte + _, err = rand.Read(commitBlob[:]) + require.NoError(t, err) + testCase := &taprootBriefcase{ - CtrlBlocks: &ctrlBlocks{ + CtrlBlocks: tlv.NewRecordT[tlv.TlvType0](ctrlBlocks{ CommitSweepCtrlBlock: sweepCtrlBlock[:], RevokeSweepCtrlBlock: revokeCtrlBlock[:], OutgoingHtlcCtrlBlocks: randResolverCtrlBlocks(t), IncomingHtlcCtrlBlocks: randResolverCtrlBlocks(t), SecondLevelCtrlBlocks: randResolverCtrlBlocks(t), - }, - TapTweaks: &tapTweaks{ + }), + TapTweaks: tlv.NewRecordT[tlv.TlvType1](tapTweaks{ AnchorTweak: anchorTweak[:], BreachedHtlcTweaks: randHtlcTweaks(t), BreachedSecondLevelHltcTweaks: randHtlcTweaks(t), - }, + }), + SettledCommitBlob: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType2](commitBlob[:]), + ), + BreachedCommitBlob: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType3](commitBlob[:]), + ), } var b bytes.Buffer diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index 407384162..b7b4d33a8 100644 --- a/contractcourt/utxonursery.go +++ b/contractcourt/utxonursery.go @@ -21,6 +21,7 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/sweep" + "github.com/lightningnetwork/lnd/tlv" ) // SUMMARY OF OUTPUT STATES @@ -1423,6 +1424,7 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, return kidOutput{ breachedOutput: makeBreachedOutput( outpoint, witnessType, nil, signDescriptor, heightHint, + fn.None[tlv.Blob](), ), isHtlc: isHtlc, originChanPoint: *originChanPoint, diff --git a/docs/release-notes/release-notes-0.18.4.md b/docs/release-notes/release-notes-0.18.4.md new file mode 100644 index 000000000..e0c23dd38 --- /dev/null +++ b/docs/release-notes/release-notes-0.18.4.md @@ -0,0 +1,95 @@ +# Release Notes +- [Bug Fixes](#bug-fixes) +- [New Features](#new-features) + - [Functional Enhancements](#functional-enhancements) + - [RPC Additions](#rpc-additions) + - [lncli Additions](#lncli-additions) +- [Improvements](#improvements) + - [Functional Updates](#functional-updates) + - [RPC Updates](#rpc-updates) + - [lncli Updates](#lncli-updates) + - [Breaking Changes](#breaking-changes) + - [Performance Improvements](#performance-improvements) +- [Technical and Architectural Updates](#technical-and-architectural-updates) + - [BOLT Spec Updates](#bolt-spec-updates) + - [Testing](#testing) + - [Database](#database) + - [Code Health](#code-health) + - [Tooling and Documentation](#tooling-and-documentation) + +# Bug Fixes +# New Features + +The main channel state machine and database now allow for processing and storing +custom Taproot script leaves, [allowing the implementation of custom channel +types](https://github.com/lightningnetwork/lnd/pull/8960). + +## Functional Enhancements + +* A new `protocol.simple-taproot-overlay-chans` configuration item/CLI flag was + added [to turn on custom channel + functionality](https://github.com/lightningnetwork/lnd/pull/8960). + +## RPC Additions + +* Some new experimental [RPCs for managing SCID + aliases](https://github.com/lightningnetwork/lnd/pull/8960) were added under + the `routerrpc` package. These methods allow manually adding and deleting SCID + aliases locally to your node. + > NOTE: these new RPC methods are marked as experimental + (`XAddLocalChanAliases` & `XDeleteLocalChanAliases`) and upon calling + them the aliases will not be communicated with the channel peer. + +* The responses for the `ListChannels`, `PendingChannels` and `ChannelBalance` + RPCs now include [a new `custom_channel_data` field that is only set for + custom channels](https://github.com/lightningnetwork/lnd/pull/8960). + +* The `routerrpc.SendPaymentV2` RPC has a new field [`first_hop_custom_records` + that allows the user to send custom p2p wire message TLV types to the first + hop of a payment](https://github.com/lightningnetwork/lnd/pull/8960). + That new field is also exposed in the `routerrpc.HtlcInterceptor`, so it can + be read and interpreted by external software. + +* The `routerrpc.HtlcInterceptor` now [allows some values of the HTLC to be + modified before they're validated by the state + machine](https://github.com/lightningnetwork/lnd/pull/8960). The fields that + can be modified are `outgoing_amount_msat` (if transported overlaid value of + HTLC doesn't match the actual BTC amount being transferred) and + `outgoing_htlc_wire_custom_records` (allow adding custom TLV values to the + p2p wire message of the forwarded HTLC). + +* A new [`invoicesrpc.HtlcModifier` RPC now allows incoming HTLCs that attempt + to satisfy an invoice to be modified before they're + validated](https://github.com/lightningnetwork/lnd/pull/8960). This allows + custom channels to determine what the actual (overlaid) value of an HTLC is, + even if that value doesn't match the actual BTC amount being transferred by + the HTLC. + +## lncli Additions + +# Improvements +## Functional Updates +## RPC Updates + +## lncli Updates + + +## Code Health +## Breaking Changes +## Performance Improvements + +# Technical and Architectural Updates +## BOLT Spec Updates + +## Testing +## Database +## Code Health +## Tooling and Documentation + +# Contributors (Alphabetical Order) + +* ffranr +* George Tsagkarelis +* Olaoluwa Osuntokun +* Oliver Gugger + diff --git a/feature/default_sets.go b/feature/default_sets.go index cc802fe85..4a9b2bf64 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -92,4 +92,8 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, + lnwire.SimpleTaprootOverlayChansOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, } diff --git a/feature/deps.go b/feature/deps.go index 64b4f2fc9..922f25214 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -79,6 +79,11 @@ var deps = depDesc{ lnwire.AnchorsZeroFeeHtlcTxOptional: {}, lnwire.ExplicitChannelTypeOptional: {}, }, + lnwire.SimpleTaprootOverlayChansOptional: { + lnwire.SimpleTaprootChannelsOptionalStaging: {}, + lnwire.TLVOnionPayloadOptional: {}, + lnwire.ScidAliasOptional: {}, + }, lnwire.RouteBlindingOptional: { lnwire.TLVOnionPayloadOptional: {}, }, diff --git a/feature/manager.go b/feature/manager.go index 0b6653985..89f1d4b6b 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -63,6 +63,9 @@ type Config struct { // NoRouteBlinding unsets route blinding feature bits. NoRouteBlinding bool + // NoTaprootOverlay unsets the taproot overlay channel feature bits. + NoTaprootOverlay bool + // CustomFeatures is a set of custom features to advertise in each // set. CustomFeatures map[Set][]lnwire.FeatureBit @@ -192,6 +195,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { raw.Unset(lnwire.Bolt11BlindedPathsOptional) raw.Unset(lnwire.Bolt11BlindedPathsRequired) } + if cfg.NoTaprootOverlay { + raw.Unset(lnwire.SimpleTaprootOverlayChansOptional) + raw.Unset(lnwire.SimpleTaprootOverlayChansRequired) + } for _, custom := range cfg.CustomFeatures[set] { if custom > set.Maximum() { return nil, fmt.Errorf("feature bit: %v "+ diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index f931b4702..817709c73 100644 --- a/funding/commitment_type_negotiation.go +++ b/funding/commitment_type_negotiation.go @@ -307,6 +307,74 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, return lnwallet.CommitmentTypeSimpleTaproot, nil + // Simple taproot channels overlay only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootOverlayChansOptional, + ) { + + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil + + // Simple taproot overlay channels with scid only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ScidAliasRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootOverlayChansOptional, + lnwire.ScidAliasOptional, + ) { + + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil + + // Simple taproot overlay channels with zero conf only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ZeroConfRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootOverlayChansOptional, + lnwire.ZeroConfOptional, + ) { + + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil + + // Simple taproot overlay channels with scid and zero conf. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ZeroConfRequired, + lnwire.ScidAliasRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootOverlayChansOptional, + lnwire.ZeroConfOptional, + lnwire.ScidAliasOptional, + ) { + + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil + // No features, use legacy commitment type. case channelFeatures.IsEmpty(): return lnwallet.CommitmentTypeLegacy, nil diff --git a/funding/manager.go b/funding/manager.go index 92c75e14b..1fa90c693 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -558,6 +558,10 @@ type Config struct { // AuxSigner is an optional signer that can be used to sign auxiliary // leaves for certain custom channel types. AuxSigner fn.Option[lnwallet.AuxSigner] + + // AuxResolver is an optional interface that can be used to modify the + // way contracts are resolved. + AuxResolver fn.Option[lnwallet.AuxContractResolver] } // Manager acts as an orchestrator/bridge between the wallet's @@ -1090,6 +1094,9 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, f.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) { chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s)) }) + f.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) { + chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s)) + }) // We create the state-machine object which wraps the database state. lnChannel, err := lnwallet.NewLightningChannel( diff --git a/funding/manager_test.go b/funding/manager_test.go index ca598cec7..dbe635377 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -4653,8 +4653,8 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) { // opening behavior with a specified fundmax flag. To give a hypothetical // example, if ANCHOR types had been introduced after the fundmax flag had been // activated, the developer would have had to code for the anchor reserve in the -// funding manager in the context of public and private channels. Otherwise -// inconsistent bahvior would have resulted when specifying fundmax for +// funding manager in the context of public and private channels. Otherwise, +// inconsistent behavior would have resulted when specifying fundmax for // different types of channel openings. // To ensure consistency this test compares a map of locally defined channel // commitment types to the list of channel types that are defined in the proto @@ -4670,6 +4670,7 @@ func TestCommitmentTypeFundmaxSanityCheck(t *testing.T) { "ANCHORS": 3, "SCRIPT_ENFORCED_LEASE": 4, "SIMPLE_TAPROOT": 5, + "SIMPLE_TAPROOT_OVERLAY": 6, } for commitmentType := range lnrpc.CommitmentType_value { diff --git a/input/input.go b/input/input.go index aef524a4c..6693e9fa8 100644 --- a/input/input.go +++ b/input/input.go @@ -6,7 +6,9 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/tlv" ) // EmptyOutPoint is a zeroed outpoint. @@ -63,6 +65,10 @@ type Input interface { // UnconfParent returns information about a possibly unconfirmed parent // tx. UnconfParent() *TxInfo + + // ResolutionBlob returns a special opaque blob to be used to + // sweep/resolve this input. + ResolutionBlob() fn.Option[tlv.Blob] } // TxInfo describes properties of a parent tx that are relevant for CPFP. @@ -106,6 +112,10 @@ type inputKit struct { // unconfParent contains information about a potential unconfirmed // parent transaction. unconfParent *TxInfo + + // resolutionBlob is an optional blob that can be used to resolve an + // input. + resolutionBlob fn.Option[tlv.Blob] } // OutPoint returns the breached output's identifier that is to be included as @@ -156,6 +166,36 @@ func (i *inputKit) UnconfParent() *TxInfo { return i.unconfParent } +// ResolutionBlob returns a special opaque blob to be used to sweep/resolve +// this input. +func (i *inputKit) ResolutionBlob() fn.Option[tlv.Blob] { + return i.resolutionBlob +} + +// inputOpts contains options for constructing a new input. +type inputOpts struct { + // resolutionBlob is an optional blob that can be used to resolve an + // input. + resolutionBlob fn.Option[tlv.Blob] +} + +// defaultInputOpts returns a new inputOpts with default values. +func defaultInputOpts() *inputOpts { + return &inputOpts{} +} + +// InputOpt is a functional option that can be used to modify the default input +// options. +type InputOpt func(*inputOpts) //nolint:revive + +// WithResolutionBlob is an option that can be used to set a resolution blob on +// for an input. +func WithResolutionBlob(b fn.Option[tlv.Blob]) InputOpt { + return func(o *inputOpts) { + o.resolutionBlob = b + } +} + // BaseInput contains all the information needed to sweep a basic // output (CSV/CLTV/no time lock). type BaseInput struct { @@ -166,15 +206,21 @@ type BaseInput struct { // sweep transaction. func MakeBaseInput(outpoint *wire.OutPoint, witnessType WitnessType, signDescriptor *SignDescriptor, heightHint uint32, - unconfParent *TxInfo) BaseInput { + unconfParent *TxInfo, opts ...InputOpt) BaseInput { + + opt := defaultInputOpts() + for _, optF := range opts { + optF(opt) + } return BaseInput{ inputKit{ - outpoint: *outpoint, - witnessType: witnessType, - signDesc: *signDescriptor, - heightHint: heightHint, - unconfParent: unconfParent, + outpoint: *outpoint, + witnessType: witnessType, + signDesc: *signDescriptor, + heightHint: heightHint, + unconfParent: unconfParent, + resolutionBlob: opt.resolutionBlob, }, } } @@ -182,10 +228,11 @@ func MakeBaseInput(outpoint *wire.OutPoint, witnessType WitnessType, // NewBaseInput allocates and assembles a new *BaseInput that can be used to // construct a sweep transaction. func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType, - signDescriptor *SignDescriptor, heightHint uint32) *BaseInput { + signDescriptor *SignDescriptor, heightHint uint32, + opts ...InputOpt) *BaseInput { input := MakeBaseInput( - outpoint, witnessType, signDescriptor, heightHint, nil, + outpoint, witnessType, signDescriptor, heightHint, nil, opts..., ) return &input @@ -195,36 +242,31 @@ func NewBaseInput(outpoint *wire.OutPoint, witnessType WitnessType, // construct a sweep transaction. func NewCsvInput(outpoint *wire.OutPoint, witnessType WitnessType, signDescriptor *SignDescriptor, heightHint uint32, - blockToMaturity uint32) *BaseInput { + blockToMaturity uint32, opts ...InputOpt) *BaseInput { - return &BaseInput{ - inputKit{ - outpoint: *outpoint, - witnessType: witnessType, - signDesc: *signDescriptor, - heightHint: heightHint, - blockToMaturity: blockToMaturity, - }, - } + input := MakeBaseInput( + outpoint, witnessType, signDescriptor, heightHint, nil, opts..., + ) + + input.blockToMaturity = blockToMaturity + + return &input } // 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 { + csvDelay uint32, cltvExpiry uint32, opts ...InputOpt) *BaseInput { - return &BaseInput{ - inputKit{ - outpoint: *outpoint, - witnessType: witnessType, - signDesc: *signDescriptor, - heightHint: heightHint, - blockToMaturity: csvDelay, - cltvExpiry: cltvExpiry, - unconfParent: nil, - }, - } + input := MakeBaseInput( + outpoint, witnessType, signDescriptor, heightHint, nil, opts..., + ) + + input.blockToMaturity = csvDelay + input.cltvExpiry = cltvExpiry + + return &input } // CraftInputScript returns a valid set of input scripts allowing this output @@ -256,16 +298,16 @@ type HtlcSucceedInput struct { // construct a sweep transaction. func MakeHtlcSucceedInput(outpoint *wire.OutPoint, signDescriptor *SignDescriptor, preimage []byte, heightHint, - blocksToMaturity uint32) HtlcSucceedInput { + blocksToMaturity uint32, opts ...InputOpt) HtlcSucceedInput { + + input := MakeBaseInput( + outpoint, HtlcAcceptedRemoteSuccess, signDescriptor, + heightHint, nil, opts..., + ) + input.blockToMaturity = blocksToMaturity return HtlcSucceedInput{ - inputKit: inputKit{ - outpoint: *outpoint, - witnessType: HtlcAcceptedRemoteSuccess, - signDesc: *signDescriptor, - heightHint: heightHint, - blockToMaturity: blocksToMaturity, - }, + inputKit: input.inputKit, preimage: preimage, } } @@ -274,16 +316,17 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint, // to spend an HTLC output for a taproot channel on the remote party's // commitment transaction. func MakeTaprootHtlcSucceedInput(op *wire.OutPoint, signDesc *SignDescriptor, - preimage []byte, heightHint, blocksToMaturity uint32) HtlcSucceedInput { + preimage []byte, heightHint, blocksToMaturity uint32, + opts ...InputOpt) HtlcSucceedInput { + + input := MakeBaseInput( + op, TaprootHtlcAcceptedRemoteSuccess, signDesc, + heightHint, nil, opts..., + ) + input.blockToMaturity = blocksToMaturity return HtlcSucceedInput{ - inputKit: inputKit{ - outpoint: *op, - witnessType: TaprootHtlcAcceptedRemoteSuccess, - signDesc: *signDesc, - heightHint: heightHint, - blockToMaturity: blocksToMaturity, - }, + inputKit: input.inputKit, preimage: preimage, } } @@ -388,7 +431,8 @@ func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer, // to spend the HTLC output on our commit using the second level timeout // transaction. func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, - signDetails *SignDetails, heightHint uint32) HtlcSecondLevelAnchorInput { + signDetails *SignDetails, heightHint uint32, + opts ...InputOpt) HtlcSecondLevelAnchorInput { // Spend an HTLC output on our local commitment tx using the // 2nd timeout transaction. @@ -408,16 +452,15 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, ) } - return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: HtlcOfferedTimeoutSecondLevelInputConfirmed, - signDesc: signDetails.SignDesc, - heightHint: heightHint, + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + HtlcOfferedTimeoutSecondLevelInputConfirmed, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + return HtlcSecondLevelAnchorInput{ + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, } @@ -429,7 +472,7 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, // sweep the second level HTLC aggregated with other transactions. func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx, signDetails *SignDetails, - heightHint uint32) HtlcSecondLevelAnchorInput { + heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput { createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, @@ -453,16 +496,15 @@ func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx, ) } - return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: TaprootHtlcLocalOfferedTimeout, - signDesc: signDetails.SignDesc, - heightHint: heightHint, + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + TaprootHtlcLocalOfferedTimeout, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + return HtlcSecondLevelAnchorInput{ + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, } @@ -473,7 +515,7 @@ func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx, // transaction. func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx, signDetails *SignDetails, preimage lntypes.Preimage, - heightHint uint32) HtlcSecondLevelAnchorInput { + heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput { // Spend an HTLC output on our local commitment tx using the 2nd // success transaction. @@ -492,18 +534,16 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx, preimage[:], signer, &desc, txn, ) } + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + HtlcAcceptedSuccessSecondLevelInputConfirmed, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: HtlcAcceptedSuccessSecondLevelInputConfirmed, - signDesc: signDetails.SignDesc, - heightHint: heightHint, - - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, SignedTx: signedTx, + inputKit: input.inputKit, createWitness: createWitness, } } @@ -513,7 +553,7 @@ func MakeHtlcSecondLevelSuccessAnchorInput(signedTx *wire.MsgTx, // commitment transaction. func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx, signDetails *SignDetails, preimage lntypes.Preimage, - heightHint uint32) HtlcSecondLevelAnchorInput { + heightHint uint32, opts ...InputOpt) HtlcSecondLevelAnchorInput { createWitness := func(signer Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, @@ -537,16 +577,15 @@ func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx, ) } - return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: TaprootHtlcAcceptedLocalSuccess, - signDesc: signDetails.SignDesc, - heightHint: heightHint, + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + TaprootHtlcAcceptedLocalSuccess, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + return HtlcSecondLevelAnchorInput{ + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, } diff --git a/input/mocks.go b/input/mocks.go index 2f38400d8..695525955 100644 --- a/input/mocks.go +++ b/input/mocks.go @@ -8,8 +8,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/mock" ) @@ -127,6 +129,17 @@ func (m *MockInput) UnconfParent() *TxInfo { return info.(*TxInfo) } +func (m *MockInput) ResolutionBlob() fn.Option[tlv.Blob] { + args := m.Called() + + info := args.Get(0) + if info == nil { + return fn.None[tlv.Blob]() + } + + return info.(fn.Option[tlv.Blob]) +} + // MockWitnessType implements the `WitnessType` interface and is used by other // packages for mock testing. type MockWitnessType struct { diff --git a/lncfg/protocol.go b/lncfg/protocol.go index d86613188..c670b1894 100644 --- a/lncfg/protocol.go +++ b/lncfg/protocol.go @@ -31,6 +31,10 @@ type ProtocolOptions struct { // experimental simple taproot chans commitment type. TaprootChans bool `long:"simple-taproot-chans" description:"if set, then lnd will create and accept requests for channels using the simple taproot commitment type"` + // TaprootOverlayChans should be set if we want to enable support for + // the experimental taproot overlay chan type. + TaprootOverlayChans bool `long:"simple-taproot-overlay-chans" description:"if set, then lnd will create and accept requests for channels using the taproot overlay commitment type"` + // 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"` diff --git a/lncfg/protocol_integration.go b/lncfg/protocol_integration.go index e568b6b9a..5c5150a0e 100644 --- a/lncfg/protocol_integration.go +++ b/lncfg/protocol_integration.go @@ -33,6 +33,10 @@ type ProtocolOptions struct { // experimental simple taproot chans commitment type. TaprootChans bool `long:"simple-taproot-chans" description:"if set, then lnd will create and accept requests for channels using the simple taproot commitment type"` + // TaprootOverlayChans should be set if we want to enable support for + // the experimental taproot overlay chan type. + TaprootOverlayChans bool `long:"simple-taproot-overlay-chans" description:"if set, then lnd will create and accept requests for channels using the taproot overlay commitment type"` + // Anchors enables anchor commitments. // TODO(halseth): transition itests to anchors instead! Anchors bool `long:"anchors" description:"enable support for anchor commitments"` diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 8c5b0513d..60a970ba6 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -229,8 +229,13 @@ const ( // to guarantee that the channel initiator has no incentives to close a leased // channel before its maturity date. CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4 - // TODO(roasbeef): need script enforce mirror type for the above as well? + // A channel that uses musig2 for the funding output, and the new tapscript + // features where relevant. CommitmentType_SIMPLE_TAPROOT CommitmentType = 5 + // Identical to the SIMPLE_TAPROOT channel type, but with extra functionality. + // This channel type also commits to additional meta data in the tapscript + // leaves for the scripts in a channel. + CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 6 ) // Enum value maps for CommitmentType. @@ -242,6 +247,7 @@ var ( 3: "ANCHORS", 4: "SCRIPT_ENFORCED_LEASE", 5: "SIMPLE_TAPROOT", + 6: "SIMPLE_TAPROOT_OVERLAY", } CommitmentType_value = map[string]int32{ "UNKNOWN_COMMITMENT_TYPE": 0, @@ -250,6 +256,7 @@ var ( "ANCHORS": 3, "SCRIPT_ENFORCED_LEASE": 4, "SIMPLE_TAPROOT": 5, + "SIMPLE_TAPROOT_OVERLAY": 6, } ) @@ -21071,7 +21078,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, 0x50, - 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x8c, 0x01, + 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0xa8, 0x01, 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, @@ -21080,420 +21087,422 @@ var file_lightning_proto_rawDesc = []byte{ 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, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, 0x50, - 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 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, 0xf6, 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, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, - 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, - 0x4c, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x89, 0x05, 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, 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, 0x1b, 0x0a, - 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, - 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, - 0x55, 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, - 0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x19, 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, 0x2a, 0xac, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, + 0x53, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4f, + 0x56, 0x45, 0x52, 0x4c, 0x41, 0x59, 0x10, 0x06, 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, 0xf6, 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, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, + 0x06, 0x2a, 0x89, 0x05, 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, 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, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, + 0x54, 0x45, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x52, 0x45, 0x51, 0x55, + 0x49, 0x52, 0x45, 0x44, 0x10, 0x18, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, + 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, + 0x4c, 0x10, 0x19, 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, 0x2a, 0xac, 0x01, + 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, - 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, - 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, - 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, - 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, - 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, - 0x32, 0xb9, 0x27, 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, 0x47, 0x0a, 0x0c, 0x47, 0x65, - 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 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, - 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 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, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, + 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, + 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, + 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, + 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0xb9, 0x27, 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, 0x47, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x65, 0x62, + 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, + 0x62, 0x75, 0x67, 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, 0x53, 0x0a, 0x10, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 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, 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, + 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 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, 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, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, - 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 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, 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, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, - 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, - 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, - 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, - 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, - 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, + 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, + 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, + 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, + 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, 0x14, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, + 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 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, + 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 ( diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 1d481650e..644930515 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -1388,8 +1388,14 @@ enum CommitmentType { A channel that uses musig2 for the funding output, and the new tapscript features where relevant. */ - // TODO(roasbeef): need script enforce mirror type for the above as well? SIMPLE_TAPROOT = 5; + + /* + Identical to the SIMPLE_TAPROOT channel type, but with extra functionality. + This channel type also commits to additional meta data in the tapscript + leaves for the scripts in a channel. + */ + SIMPLE_TAPROOT_OVERLAY = 6; } message ChannelConstraints { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index db3ec2cb3..5531d1ebb 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -4568,10 +4568,11 @@ "STATIC_REMOTE_KEY", "ANCHORS", "SCRIPT_ENFORCED_LEASE", - "SIMPLE_TAPROOT" + "SIMPLE_TAPROOT", + "SIMPLE_TAPROOT_OVERLAY" ], "default": "UNKNOWN_COMMITMENT_TYPE", - "title": "- 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.\n - SIMPLE_TAPROOT: TODO(roasbeef): need script enforce mirror type for the above as well?" + "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.\n - SIMPLE_TAPROOT: A channel that uses musig2 for the funding output, and the new tapscript\nfeatures where relevant.\n - SIMPLE_TAPROOT_OVERLAY: Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.\nThis channel type also commits to additional meta data in the tapscript\nleaves for the scripts in a channel." }, "lnrpcConnectPeerRequest": { "type": "object", diff --git a/lnwallet/aux_resolutions.go b/lnwallet/aux_resolutions.go new file mode 100644 index 000000000..e3b04fc5a --- /dev/null +++ b/lnwallet/aux_resolutions.go @@ -0,0 +1,89 @@ +package lnwallet + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" +) + +// CloseType is an enum that represents the type of close that we are trying to +// resolve. +type CloseType uint8 + +const ( + // LocalForceClose represents a local force close. + LocalForceClose CloseType = iota + + // RemoteForceClose represents a remote force close. + RemoteForceClose + + // Breach represents a breach by the remote party. + Breach +) + +// ResolutionReq is used to ask an outside sub-system for additional +// information needed to resolve a contract. +type ResolutionReq struct { + // ChanPoint is the channel point of the channel that we are trying to + // resolve. + ChanPoint wire.OutPoint + + // ShortChanID is the short channel ID of the channel that we are + // trying to resolve. + ShortChanID lnwire.ShortChannelID + + // Initiator is a bool if we're the initiator of the channel. + Initiator bool + + // CommitBlob is an optional commit blob for the channel. + CommitBlob fn.Option[tlv.Blob] + + // FundingBlob is an optional funding blob for the channel. + FundingBlob fn.Option[tlv.Blob] + + // Type is the type of the witness that we are trying to resolve. + Type input.WitnessType + + // CloseType is the type of close that we are trying to resolve. + CloseType CloseType + + // CommitTx is the force close commitment transaction. + CommitTx *wire.MsgTx + + // CommitFee is the fee that was paid for the commitment transaction. + CommitFee btcutil.Amount + + // ContractPoint is the outpoint of the contract we're trying to + // resolve. + ContractPoint wire.OutPoint + + // SignDesc is the sign descriptor for the contract. + SignDesc input.SignDescriptor + + // KeyRing is the key ring for the channel. + KeyRing *CommitmentKeyRing + + // CsvDelay is the CSV delay for the local output for this commitment. + CsvDelay uint32 + + // BreachCsvDelay is the CSV delay for the remote output. This is only + // set when the CloseType is Breach. This indicates the CSV delay to + // use for the remote party's to_local delayed output, that is now + // rightfully ours in a breach situation. + BreachCsvDelay fn.Option[uint32] + + // CltvDelay is the CLTV delay for the outpoint. + CltvDelay fn.Option[uint32] +} + +// AuxContractResolver is an interface that is used to resolve contracts that +// may need additional outside information to resolve correctly. +type AuxContractResolver interface { + // ResolveContract is called to resolve a contract that needs + // additional information to resolve properly. If no extra information + // is required, a nil Result error is returned. + ResolveContract(ResolutionReq) fn.Result[tlv.Blob] +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 8f7513f10..f3e076950 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -764,6 +764,10 @@ type LightningChannel struct { // custom channel variants. auxSigner fn.Option[AuxSigner] + // auxResolver is an optional component that can be used to modify the + // way contracts are resolved. + auxResolver fn.Option[AuxContractResolver] + // Capacity is the total capacity of this channel. Capacity btcutil.Amount @@ -824,8 +828,9 @@ type channelOpts struct { localNonce *musig2.Nonces remoteNonce *musig2.Nonces - leafStore fn.Option[AuxLeafStore] - auxSigner fn.Option[AuxSigner] + leafStore fn.Option[AuxLeafStore] + auxSigner fn.Option[AuxSigner] + auxResolver fn.Option[AuxContractResolver] skipNonceInit bool } @@ -871,6 +876,14 @@ func WithAuxSigner(signer AuxSigner) ChannelOpt { } } +// WithAuxResolver is used to specify a custom aux contract resolver for the +// channel. +func WithAuxResolver(resolver AuxContractResolver) ChannelOpt { + return func(o *channelOpts) { + o.auxResolver = fn.Some[AuxContractResolver](resolver) + } +} + // defaultChannelOpts returns the set of default options for a new channel. func defaultChannelOpts() *channelOpts { return &channelOpts{} @@ -924,6 +937,7 @@ func NewLightningChannel(signer input.Signer, Signer: signer, leafStore: opts.leafStore, auxSigner: opts.auxSigner, + auxResolver: opts.auxResolver, sigPool: sigPool, currentHeight: localCommit.CommitHeight, commitChains: commitChains, @@ -1884,6 +1898,10 @@ type HtlcRetribution struct { // this HTLC was offered by us. This flag is used determine the exact // witness type should be used to sweep the output. IsIncoming bool + + // ResolutionBlob is a blob used for aux channels that permits a + // spender of this output to claim all funds. + ResolutionBlob fn.Option[tlv.Blob] } // BreachRetribution contains all the data necessary to bring a channel @@ -1953,6 +1971,14 @@ type BreachRetribution struct { // breaching commitment transaction. This allows downstream clients to // have access to the public keys used in the scripts. KeyRing *CommitmentKeyRing + + // LocalResolutionBlob is a blob used for aux channels that permits an + // honest party to sweep the local commitment output. + LocalResolutionBlob fn.Option[tlv.Blob] + + // RemoteResolutionBlob is a blob used for aux channels that permits an + // honest party to sweep the remote commitment output. + RemoteResolutionBlob fn.Option[tlv.Blob] } // NewBreachRetribution creates a new fully populated BreachRetribution for the @@ -1964,7 +1990,9 @@ type BreachRetribution struct { // the required fields then ErrRevLogDataMissing will be returned. func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, breachHeight uint32, spendTx *wire.MsgTx, - leafStore fn.Option[AuxLeafStore]) (*BreachRetribution, error) { + leafStore fn.Option[AuxLeafStore], + auxResolver fn.Option[AuxContractResolver]) (*BreachRetribution, + error) { // Query the on-disk revocation log for the snapshot which was recorded // at this particular state num. Based on whether a legacy revocation @@ -2127,6 +2155,38 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, return nil, err } } + + // At this point, we'll check to see if we need any extra + // resolution data for this output. + resolveReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + FundingBlob: chanState.CustomBlob, + Type: input.TaprootRemoteCommitSpend, + CloseType: Breach, + CommitTx: spendTx, + SignDesc: *br.LocalOutputSignDesc, + KeyRing: keyRing, + CsvDelay: ourDelay, + BreachCsvDelay: fn.Some(theirDelay), + CommitFee: chanState.RemoteCommitment.CommitFee, + } + if revokedLog != nil { + resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt() + } + + resolveBlob := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + return a.ResolveContract(resolveReq) + }, + ) + if err := resolveBlob.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + br.LocalResolutionBlob = resolveBlob.Option() } // Similarly, if their balance exceeds the remote party's dust limit, @@ -2174,6 +2234,37 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, return nil, err } } + + // At this point, we'll check to see if we need any extra + // resolution data for this output. + resolveReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + FundingBlob: chanState.CustomBlob, + Type: input.TaprootCommitmentRevoke, + CloseType: Breach, + CommitTx: spendTx, + SignDesc: *br.RemoteOutputSignDesc, + KeyRing: keyRing, + CsvDelay: theirDelay, + BreachCsvDelay: fn.Some(theirDelay), + CommitFee: chanState.RemoteCommitment.CommitFee, + } + if revokedLog != nil { + resolveReq.CommitBlob = revokedLog.CustomBlob.ValOpt() + } + resolveBlob := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + return a.ResolveContract(resolveReq) + }, + ) + if err := resolveBlob.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + br.RemoteResolutionBlob = resolveBlob.Option() } // Finally, with all the necessary data constructed, we can pad the @@ -6473,6 +6564,11 @@ type CommitOutputResolution struct { // that pay to the local party within the broadcast commitment // transaction. MaturityDelay uint32 + + // ResolutionBlob is a blob used for aux channels that permits a + // spender of the output to properly resolve it in the case of a force + // close. + ResolutionBlob fn.Option[tlv.Blob] } // UnilateralCloseSummary describes the details of a detected unilateral @@ -6530,7 +6626,9 @@ type UnilateralCloseSummary struct { func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer, commitSpend *chainntnfs.SpendDetail, remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey, - leafStore fn.Option[AuxLeafStore]) (*UnilateralCloseSummary, error) { + leafStore fn.Option[AuxLeafStore], + auxResolver fn.Option[AuxContractResolver]) (*UnilateralCloseSummary, + error) { // First, we'll generate the commitment point and the revocation point // so we can re-construct the HTLC state and also our payment key. @@ -6554,11 +6652,17 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, // Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we // had on their commitment transaction. - var leaseExpiry uint32 + var ( + leaseExpiry uint32 + selfPoint *wire.OutPoint + localBalance int64 + isRemoteInitiator = !chanState.IsInitiator + commitTxBroadcast = commitSpend.SpendingTx + ) + if chanState.ChanType.HasLeaseExpiration() { leaseExpiry = chanState.ThawHeight } - isRemoteInitiator := !chanState.IsInitiator htlcResolutions, err := extractHtlcResolutions( chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType, signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, @@ -6567,12 +6671,10 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, auxResult.AuxLeaves, ) if err != nil { - return nil, fmt.Errorf("unable to create htlc "+ - "resolutions: %v", err) + return nil, fmt.Errorf("unable to create htlc resolutions: %w", + err) } - commitTxBroadcast := commitSpend.SpendingTx - // Before we can generate the proper sign descriptor, we'll need to // locate the output index of our non-delayed output on the commitment // transaction. @@ -6587,14 +6689,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ - "script: %v", err) + "script: %w", err) } - - var ( - selfPoint *wire.OutPoint - localBalance int64 - ) - for outputIndex, txOut := range commitTxBroadcast.TxOut { if bytes.Equal(txOut.PkScript, selfScript.PkScript()) { selfPoint = &wire.OutPoint{ @@ -6657,6 +6753,35 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, return nil, err } } + + // At this point, we'll check to see if we need any extra + // resolution data for this output. + resolveReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.RemoteCommitment.CustomBlob, + FundingBlob: chanState.CustomBlob, + Type: input.TaprootRemoteCommitSpend, + CloseType: RemoteForceClose, + CommitTx: commitTxBroadcast, + ContractPoint: *selfPoint, + SignDesc: commitResolution.SelfOutputSignDesc, + KeyRing: keyRing, + CsvDelay: maturityDelay, + CommitFee: chanState.RemoteCommitment.CommitFee, + } + resolveBlob := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + return a.ResolveContract(resolveReq) + }, + ) + if err := resolveBlob.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + commitResolution.ResolutionBlob = resolveBlob.Option() } closeSummary := channeldb.ChannelCloseSummary{ @@ -7513,7 +7638,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { localCommitment := lc.channelState.LocalCommitment summary, err := NewLocalForceCloseSummary( lc.channelState, lc.Signer, commitTx, - localCommitment.CommitHeight, lc.leafStore, + localCommitment.CommitHeight, lc.leafStore, lc.auxResolver, ) if err != nil { return nil, fmt.Errorf("unable to gen force close "+ @@ -7531,7 +7656,9 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { // transaction corresponding to localCommit. func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer, commitTx *wire.MsgTx, stateNum uint64, - leafStore fn.Option[AuxLeafStore]) (*LocalForceCloseSummary, error) { + leafStore fn.Option[AuxLeafStore], + auxResolver fn.Option[AuxContractResolver]) (*LocalForceCloseSummary, + error) { // Re-derive the original pkScript for to-self output within the // commitment transaction. We'll need this to find the corresponding @@ -7655,6 +7782,35 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, return nil, err } } + + // At this point, we'll check to see if we need any extra + // resolution data for this output. + resolveBlob := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + //nolint:lll + return a.ResolveContract(ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.LocalCommitment.CustomBlob, + FundingBlob: chanState.CustomBlob, + Type: input.TaprootLocalCommitSpend, + CloseType: LocalForceClose, + CommitTx: commitTx, + ContractPoint: commitResolution.SelfOutPoint, + SignDesc: commitResolution.SelfOutputSignDesc, + KeyRing: keyRing, + CsvDelay: csvTimeout, + CommitFee: chanState.LocalCommitment.CommitFee, + }) + }, + ) + if err := resolveBlob.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + commitResolution.ResolutionBlob = resolveBlob.Option() } // Once the delay output has been found (if it exists), then we'll also diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index c56bce23b..3adb21636 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -6043,6 +6043,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -6193,6 +6194,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -6211,6 +6213,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { aliceRemoteChainTip.Commitment, aliceChannel.channelState.RemoteNextRevocation, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -7092,6 +7095,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { breachRet, err := NewBreachRetribution( aliceChannel.channelState, revokedStateNum, 100, breachTx, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create breach retribution") @@ -10653,6 +10657,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10661,6 +10666,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10707,6 +10713,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { br, err := NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err) @@ -10719,6 +10726,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { br, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err) assertRetribution(br, 1, 0) @@ -10728,6 +10736,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, breachTx, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) @@ -10736,6 +10745,7 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, nil, fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 2a4462462..0ab18ef02 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -599,6 +600,68 @@ type MessageSigner interface { doubleHash bool) (*ecdsa.Signature, error) } +// AddrWithKey wraps a normal addr, but also includes the internal key for the +// delivery addr if known. +type AddrWithKey struct { + lnwire.DeliveryAddress + + InternalKey fn.Option[keychain.KeyDescriptor] + + // TODO(roasbeef): consolidate w/ instance in chan closer +} + +// InternalKeyForAddr returns the internal key associated with a taproot +// address. +func InternalKeyForAddr(wallet WalletController, netParams *chaincfg.Params, + deliveryScript []byte) (fn.Option[keychain.KeyDescriptor], error) { + + none := fn.None[keychain.KeyDescriptor]() + + pkScript, err := txscript.ParsePkScript(deliveryScript) + if err != nil { + return none, err + } + addr, err := pkScript.Address(netParams) + if err != nil { + return none, err + } + + // If it's not a taproot address, we don't require to know the internal + // key in the first place. So we don't return an error here, but also no + // internal key. + _, isTaproot := addr.(*btcutil.AddressTaproot) + if !isTaproot { + return none, nil + } + + walletAddr, err := wallet.AddressInfo(addr) + if err != nil { + return none, err + } + + // No wallet addr. No error, but we'll return an nil error value here, + // as callers can use the .Option() method to get an option value. + if walletAddr == nil { + return none, nil + } + + pubKeyAddr, ok := walletAddr.(waddrmgr.ManagedPubKeyAddress) + if !ok { + return none, fmt.Errorf("expected pubkey addr, got %T", + pubKeyAddr) + } + + _, derivationPath, _ := pubKeyAddr.DerivationInfo() + + return fn.Some[keychain.KeyDescriptor](keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(derivationPath.Account), + Index: derivationPath.Index, + }, + PubKey: pubKeyAddr.PubKey(), + }), nil +} + // WalletDriver represents a "driver" for a particular concrete // WalletController implementation. A driver is identified by a globally unique // string identifier along with a 'New()' method which is responsible for diff --git a/lnwallet/mock.go b/lnwallet/mock.go index 82b9e19c2..f99f5c0b9 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -493,3 +493,14 @@ func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState, return args.Error(0) } + +type MockAuxContractResolver struct{} + +// ResolveContract is called to resolve a contract that needs +// additional information to resolve properly. If no extra information +// is required, a nil Result error is returned. +func (*MockAuxContractResolver) ResolveContract( + ResolutionReq) fn.Result[tlv.Blob] { + + return fn.Ok[tlv.Blob](nil) +} diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 7f1a89c22..4c4f58f8b 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -2,6 +2,7 @@ package lnwallet import ( "errors" + "fmt" "net" "sync" @@ -50,6 +51,11 @@ const ( // channels that use a musig2 funding output and the tapscript tree // where relevant for the commitment transaction pk scripts. CommitmentTypeSimpleTaproot + + // CommitmentTypeSimpleTaprootOverlay builds on the existing + // CommitmentTypeSimpleTaproot type but layers on a special overlay + // protocol. + CommitmentTypeSimpleTaprootOverlay ) // HasStaticRemoteKey returns whether the commitment type supports remote @@ -59,8 +65,11 @@ func (c CommitmentType) HasStaticRemoteKey() bool { case CommitmentTypeTweakless, CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, - CommitmentTypeSimpleTaproot: + CommitmentTypeSimpleTaproot, + CommitmentTypeSimpleTaprootOverlay: + return true + default: return false } @@ -71,8 +80,11 @@ func (c CommitmentType) HasAnchors() bool { switch c { case CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, - CommitmentTypeSimpleTaproot: + CommitmentTypeSimpleTaproot, + CommitmentTypeSimpleTaprootOverlay: + return true + default: return false } @@ -80,7 +92,8 @@ func (c CommitmentType) HasAnchors() bool { // IsTaproot returns true if the channel type is a taproot channel. func (c CommitmentType) IsTaproot() bool { - return c == CommitmentTypeSimpleTaproot + return c == CommitmentTypeSimpleTaproot || + c == CommitmentTypeSimpleTaprootOverlay } // String returns the name of the CommitmentType. @@ -96,6 +109,8 @@ func (c CommitmentType) String() string { return "script-enforced-lease" case CommitmentTypeSimpleTaproot: return "simple-taproot" + case CommitmentTypeSimpleTaprootOverlay: + return "simple-taproot-overlay" default: return "invalid" } @@ -424,7 +439,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.FrozenBit } - if req.CommitType == CommitmentTypeSimpleTaproot { + if req.CommitType.IsTaproot() { chanType |= channeldb.SimpleTaprootFeatureBit } @@ -440,7 +455,15 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.ScidAliasFeatureBit } - if req.TapscriptRoot.IsSome() { + taprootOverlay := req.CommitType == CommitmentTypeSimpleTaprootOverlay + switch { + case taprootOverlay && req.TapscriptRoot.IsNone(): + fallthrough + case !taprootOverlay && req.TapscriptRoot.IsSome(): + return nil, fmt.Errorf("taproot overlay chans must be set " + + "with tapscript root") + + case taprootOverlay && req.TapscriptRoot.IsSome(): chanType |= channeldb.TapscriptRootBit } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index f60856113..a9018437d 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -983,7 +983,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg TaprootPubkey, true, DefaultAccountName, ) }, - Musig2: req.CommitType == CommitmentTypeSimpleTaproot, + Musig2: req.CommitType.IsTaproot(), } fundingIntent, err = req.ChanFunder.ProvisionChannel( fundingReq, diff --git a/lnwire/features.go b/lnwire/features.go index c5ae014f7..c597b0398 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -273,6 +273,14 @@ const ( // a BOLT 11 invoice. Bolt11BlindedPathsOptional = 263 + // SimpleTaprootOverlayChansRequired is a required bit that indicates + // support for the special custom taproot overlay channel. + SimpleTaprootOverlayChansOptional = 2025 + + // SimpleTaprootOverlayChansRequired is a required bit that indicates + // support for the special custom taproot overlay channel. + SimpleTaprootOverlayChansRequired = 2026 + // MaxBolt11Feature is the maximum feature bit value allowed in bolt 11 // invoices. // @@ -339,6 +347,8 @@ var Features = map[FeatureBit]string{ SimpleTaprootChannelsOptionalFinal: "simple-taproot-chans", SimpleTaprootChannelsRequiredStaging: "simple-taproot-chans-x", SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x", + SimpleTaprootOverlayChansOptional: "taproot-overlay-chans", + SimpleTaprootOverlayChansRequired: "taproot-overlay-chans", Bolt11BlindedPathsOptional: "bolt-11-blinded-paths", Bolt11BlindedPathsRequired: "bolt-11-blinded-paths", } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 374b85c2b..6b9630f58 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -1920,22 +1920,28 @@ func TestLightningWireProtocol(t *testing.T) { }, } for _, test := range tests { - var config *quick.Config + t.Run(test.msgType.String(), func(t *testing.T) { + var config *quick.Config - // If the type defined is within the custom type gen map above, - // then we'll modify the default config to use this Value - // function that knows how to generate the proper types. - if valueGen, ok := customTypeGen[test.msgType]; ok { - config = &quick.Config{ - Values: valueGen, + // If the type defined is within the custom type gen + // map above, then we'll modify the default config to + // use this Value function that knows how to generate + // the proper types. + if valueGen, ok := customTypeGen[test.msgType]; ok { + config = &quick.Config{ + Values: valueGen, + } } - } - t.Logf("Running fuzz tests for msgType=%v", test.msgType) - if err := quick.Check(test.scenario, config); err != nil { - t.Fatalf("fuzz checks for msg=%v failed: %v", - test.msgType, err) - } + t.Logf("Running fuzz tests for msgType=%v", + test.msgType) + + err := quick.Check(test.scenario, config) + if err != nil { + t.Fatalf("fuzz checks for msg=%v failed: %v", + test.msgType, err) + } + }) } } diff --git a/peer/brontide.go b/peer/brontide.go index 8f35f0461..28b3e0dcd 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -13,13 +13,11 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" - "github.com/btcsuite/btcwallet/waddrmgr" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/buffer" "github.com/lightningnetwork/lnd/build" @@ -37,6 +35,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" @@ -397,6 +396,10 @@ type Config struct { // leaves for certain custom channel types. AuxSigner fn.Option[lnwallet.AuxSigner] + // AuxResolver is an optional interface that can be used to modify the + // way contracts are resolved. + AuxResolver fn.Option[lnwallet.AuxContractResolver] + // PongBuf is a slice we'll reuse instead of allocating memory on the // heap. Since only reads will occur and no writes, there is no need // for any synchronization primitives. As a result, it's safe to share @@ -902,71 +905,31 @@ func (p *Brontide) QuitSignal() <-chan struct{} { return p.quit } -// internalKeyForAddr returns the internal key associated with a taproot -// address. -func internalKeyForAddr(wallet *lnwallet.LightningWallet, - deliveryScript []byte) (fn.Option[btcec.PublicKey], error) { - - none := fn.None[btcec.PublicKey]() - - pkScript, err := txscript.ParsePkScript(deliveryScript) - if err != nil { - return none, err - } - addr, err := pkScript.Address(&wallet.Cfg.NetParams) - if err != nil { - return none, err - } - - // If it's not a taproot address, we don't require to know the internal - // key in the first place. So we don't return an error here, but also no - // internal key. - _, isTaproot := addr.(*btcutil.AddressTaproot) - if !isTaproot { - return none, nil - } - - walletAddr, err := wallet.AddressInfo(addr) - if err != nil { - return none, err - } - - // If the address isn't known to the wallet, we can't determine the - // internal key. - if walletAddr == nil { - return none, nil - } - - pubKeyAddr, ok := walletAddr.(waddrmgr.ManagedPubKeyAddress) - if !ok { - return none, fmt.Errorf("expected pubkey addr, got %T", - pubKeyAddr) - } - - return fn.Some(*pubKeyAddr.PubKey()), nil -} - // addrWithInternalKey takes a delivery script, then attempts to supplement it // with information related to the internal key for the addr, but only if it's // a taproot addr. func (p *Brontide) addrWithInternalKey( - deliveryScript []byte) fn.Result[chancloser.DeliveryAddrWithKey] { + deliveryScript []byte) (*chancloser.DeliveryAddrWithKey, error) { - // TODO(roasbeef): not compatible with external shutdown addr? // Currently, custom channels cannot be created with external upfront // shutdown addresses, so this shouldn't be an issue. We only require // the internal key for taproot addresses to be able to provide a non // inclusion proof of any scripts. - - internalKey, err := internalKeyForAddr(p.cfg.Wallet, deliveryScript) + internalKeyDesc, err := lnwallet.InternalKeyForAddr( + p.cfg.Wallet, &p.cfg.Wallet.Cfg.NetParams, deliveryScript, + ) if err != nil { - return fn.Err[chancloser.DeliveryAddrWithKey](err) + return nil, fmt.Errorf("unable to fetch internal key: %w", err) } - return fn.Ok(chancloser.DeliveryAddrWithKey{ + return &chancloser.DeliveryAddrWithKey{ DeliveryAddress: deliveryScript, - InternalKey: internalKey, - }) + InternalKey: fn.MapOption( + func(desc keychain.KeyDescriptor) btcec.PublicKey { + return *desc.PubKey + }, + )(internalKeyDesc), + }, nil } // loadActiveChannels creates indexes within the peer for tracking all active @@ -1047,6 +1010,14 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) { chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s)) }) + p.cfg.AuxResolver.WhenSome( + func(s lnwallet.AuxContractResolver) { + chanOpts = append( + chanOpts, lnwallet.WithAuxResolver(s), + ) + }, + ) + lnChan, err := lnwallet.NewLightningChannel( p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts..., ) @@ -1211,7 +1182,7 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( addr, err := p.addrWithInternalKey( info.DeliveryScript.Val, - ).Unpack() + ) if err != nil { shutdownInfoErr = fmt.Errorf("unable to make "+ "delivery addr: %w", err) @@ -2977,7 +2948,7 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) ( return nil, fmt.Errorf("unable to estimate fee") } - addr, err := p.addrWithInternalKey(deliveryScript).Unpack() + addr, err := p.addrWithInternalKey(deliveryScript) if err != nil { return nil, fmt.Errorf("unable to parse addr: %w", err) } @@ -3224,7 +3195,7 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( closingParty = lntypes.Local } - addr, err := p.addrWithInternalKey(deliveryScript).Unpack() + addr, err := p.addrWithInternalKey(deliveryScript) if err != nil { return nil, fmt.Errorf("unable to parse addr: %w", err) } @@ -3256,7 +3227,7 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( // createChanCloser constructs a ChanCloser from the passed parameters and is // used to de-duplicate code. func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, - deliveryScript chancloser.DeliveryAddrWithKey, + deliveryScript *chancloser.DeliveryAddrWithKey, fee chainfee.SatPerKWeight, req *htlcswitch.ChanClose, closer lntypes.ChannelParty) (*chancloser.ChanCloser, error) { @@ -3291,7 +3262,7 @@ func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, ChainParams: &p.cfg.Wallet.Cfg.NetParams, Quit: p.quit, }, - deliveryScript, + *deliveryScript, fee, uint32(startingHeight), req, @@ -3350,7 +3321,7 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { return } } - addr, err := p.addrWithInternalKey(deliveryScript).Unpack() + addr, err := p.addrWithInternalKey(deliveryScript) if err != nil { err = fmt.Errorf("unable to parse addr for channel "+ "%v: %w", req.ChanPoint, err) @@ -4293,6 +4264,9 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { p.cfg.AuxSigner.WhenSome(func(s lnwallet.AuxSigner) { chanOpts = append(chanOpts, lnwallet.WithAuxSigner(s)) }) + p.cfg.AuxResolver.WhenSome(func(s lnwallet.AuxContractResolver) { + chanOpts = append(chanOpts, lnwallet.WithAuxResolver(s)) + }) // If not already active, we'll add this channel to the set of active // channels, so we can look it up later easily according to its channel diff --git a/routing/bandwidth.go b/routing/bandwidth.go index a193c654a..3b80dadc7 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -47,7 +47,8 @@ type TlvTrafficShaper interface { // is a custom channel that should be handled by the traffic shaper, the // HandleTraffic method should be called first. PaymentBandwidth(htlcBlob, commitmentBlob fn.Option[tlv.Blob], - linkBandwidth lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) + linkBandwidth, + htlcAmt lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) } // AuxHtlcModifier is an interface that allows the sender to modify the outgoing @@ -191,6 +192,7 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, commitmentBlob := link.CommitmentCustomBlob() auxBandwidth, err := ts.PaymentBandwidth( b.firstHopBlob, commitmentBlob, linkBandwidth, + amount, ) if err != nil { return bandwidthErr(fmt.Errorf("failed to get "+ diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index 4876f09c7..4872b5a7e 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -148,7 +148,7 @@ func (*mockTrafficShaper) ShouldHandleTraffic(_ lnwire.ShortChannelID, // is a custom channel that should be handled by the traffic shaper, the // HandleTraffic method should be called first. func (*mockTrafficShaper) PaymentBandwidth(_, _ fn.Option[tlv.Blob], - linkBandwidth lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { + linkBandwidth, _ lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { return linkBandwidth, nil } diff --git a/rpcserver.go b/rpcserver.go index c9edf852a..e44d9b78c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2309,6 +2309,29 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, *channelType = lnwire.ChannelType(*fv) + case lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY: + // If the taproot overlay channel type is being set, then the + // channel MUST be private. + if !in.Private { + return nil, fmt.Errorf("taproot overlay channels " + + "must be private") + } + + channelType = new(lnwire.ChannelType) + fv := lnwire.NewRawFeatureVector( + lnwire.SimpleTaprootOverlayChansRequired, + ) + + if in.ZeroConf { + fv.Set(lnwire.ZeroConfRequired) + } + + if in.ScidAlias { + fv.Set(lnwire.ScidAliasRequired) + } + + *channelType = lnwire.ChannelType(*fv) + default: return nil, fmt.Errorf("unhandled request channel type %v", in.CommitmentType) @@ -4533,6 +4556,9 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { // first check whether it has anchors, since in that case it would also // be tweakless. switch { + case chanType.HasTapscriptRoot(): + return lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY + case chanType.IsTaproot(): return lnrpc.CommitmentType_SIMPLE_TAPROOT @@ -4544,6 +4570,7 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { case chanType.IsTweakless(): return lnrpc.CommitmentType_STATIC_REMOTE_KEY + default: return lnrpc.CommitmentType_LEGACY diff --git a/rpcserver_test.go b/rpcserver_test.go index 9dd3c3f86..53ec6d0ac 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -77,3 +77,53 @@ func TestAuxDataParser(t *testing.T) { require.NotNil(t, resp) require.Equal(t, []byte{0x00, 0x00}, resp.CustomChannelData) } + +// TestRpcCommitmentType tests the rpcCommitmentType returns the corect +// commitment type given a channel type. +func TestRpcCommitmentType(t *testing.T) { + tests := []struct { + name string + chanType channeldb.ChannelType + want lnrpc.CommitmentType + }{ + { + name: "tapscript overlay", + chanType: channeldb.SimpleTaprootFeatureBit | + channeldb.TapscriptRootBit, + want: lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY, + }, + { + name: "simple taproot", + chanType: channeldb.SimpleTaprootFeatureBit, + want: lnrpc.CommitmentType_SIMPLE_TAPROOT, + }, + { + name: "lease expiration", + chanType: channeldb.LeaseExpirationBit, + want: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, + }, + { + name: "anchors", + chanType: channeldb.AnchorOutputsBit, + want: lnrpc.CommitmentType_ANCHORS, + }, + { + name: "tweakless", + chanType: channeldb.SingleFunderTweaklessBit, + want: lnrpc.CommitmentType_STATIC_REMOTE_KEY, + }, + { + name: "legacy", + chanType: channeldb.SingleFunderBit, + want: lnrpc.CommitmentType_LEGACY, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal( + t, tt.want, rpcCommitmentType(tt.chanType), + ) + }) + } +} diff --git a/sample-lnd.conf b/sample-lnd.conf index 56d66b595..05ebd265d 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1324,6 +1324,9 @@ ; Set to enable support for the experimental taproot channel type. ; protocol.simple-taproot-chans=false +; Set to enable support for the experimental taproot overlay channel type. +; protocol.simple-taproot-overlay-chans=false + ; Set to disable blinded route forwarding. ; protocol.no-route-blinding=false diff --git a/server.go b/server.go index 8b142f87a..39600fb6f 100644 --- a/server.go +++ b/server.go @@ -18,6 +18,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/txscript" @@ -521,6 +522,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, var serializedPubKey [33]byte copy(serializedPubKey[:], nodeKeyDesc.PubKey.SerializeCompressed()) + netParams := cfg.ActiveNetParams.Params + // Initialize the sphinx router. replayLog := htlcswitch.NewDecayedLog( dbs.DecayedLogDB, cc.ChainNotifier, @@ -545,6 +548,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr, readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout, ) + // If the taproot overlay flag is set, but we don't have an aux funding + // controller, then we'll exit as this is incompatible. + if cfg.ProtocolOptions.TaprootOverlayChans && + implCfg.AuxFundingController.IsNone() { + + return nil, fmt.Errorf("taproot overlay flag set, but not " + + "aux controllers") + } + //nolint:lll featureMgr, err := feature.NewManager(feature.Config{ NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(), @@ -558,6 +570,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(), CustomFeatures: cfg.ProtocolOptions.CustomFeatures(), NoTaprootChans: !cfg.ProtocolOptions.TaprootChans, + NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans, NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(), }) if err != nil { @@ -1123,18 +1136,22 @@ func newServer(cfg *Config, listenAddrs []net.Addr, aggregator := sweep.NewBudgetAggregator( cc.FeeEstimator, sweep.DefaultMaxInputsPerTx, + s.implCfg.AuxSweeper, ) s.txPublisher = sweep.NewTxPublisher(sweep.TxPublisherConfig{ - Signer: cc.Wallet.Cfg.Signer, - Wallet: cc.Wallet, - Estimator: cc.FeeEstimator, - Notifier: cc.ChainNotifier, + Signer: cc.Wallet.Cfg.Signer, + Wallet: cc.Wallet, + Estimator: cc.FeeEstimator, + Notifier: cc.ChainNotifier, + AuxSweeper: s.implCfg.AuxSweeper, }) s.sweeper = sweep.New(&sweep.UtxoSweeperConfig{ - FeeEstimator: cc.FeeEstimator, - GenSweepScript: newSweepPkScriptGen(cc.Wallet), + FeeEstimator: cc.FeeEstimator, + GenSweepScript: newSweepPkScriptGen( + cc.Wallet, s.cfg.ActiveNetParams.Params, + ), Signer: cc.Wallet.Cfg.Signer, Wallet: newSweeperWallet(cc.Wallet), Mempool: cc.MempoolNotifier, @@ -1177,10 +1194,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, s.breachArbitrator = contractcourt.NewBreachArbitrator( &contractcourt.BreachConfig{ - CloseLink: closeLink, - DB: s.chanStateDB, - Estimator: s.cc.FeeEstimator, - GenSweepScript: newSweepPkScriptGen(cc.Wallet), + CloseLink: closeLink, + DB: s.chanStateDB, + Estimator: s.cc.FeeEstimator, + GenSweepScript: newSweepPkScriptGen( + cc.Wallet, s.cfg.ActiveNetParams.Params, + ), Notifier: cc.ChainNotifier, PublishTransaction: cc.Wallet.PublishTransaction, ContractBreaches: contractBreaches, @@ -1188,6 +1207,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, Store: contractcourt.NewRetributionStore( dbs.ChanStateDB, ), + AuxSweeper: s.implCfg.AuxSweeper, }, ) @@ -1196,8 +1216,17 @@ func newServer(cfg *Config, listenAddrs []net.Addr, ChainHash: *s.cfg.ActiveNetParams.GenesisHash, IncomingBroadcastDelta: lncfg.DefaultIncomingBroadcastDelta, OutgoingBroadcastDelta: lncfg.DefaultOutgoingBroadcastDelta, - NewSweepAddr: newSweepPkScriptGen(cc.Wallet), - PublishTx: cc.Wallet.PublishTransaction, + NewSweepAddr: func() ([]byte, error) { + addr, err := newSweepPkScriptGen( + cc.Wallet, netParams, + )().Unpack() + if err != nil { + return nil, err + } + + return addr.DeliveryAddress, nil + }, + PublishTx: cc.Wallet.PublishTransaction, DeliverResolutionMsg: func(msgs ...contractcourt.ResolutionMsg) error { for _, msg := range msgs { err := s.htlcSwitch.ProcessContractResolution(msg) @@ -1304,6 +1333,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, }, AuxLeafStore: implCfg.AuxLeafStore, AuxSigner: implCfg.AuxSigner, + AuxResolver: implCfg.AuxContractResolver, }, dbs.ChanStateDB) // Select the configuration and funding parameters for Bitcoin. @@ -1553,6 +1583,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint, AuxFundingController: implCfg.AuxFundingController, AuxSigner: implCfg.AuxSigner, + AuxResolver: implCfg.AuxContractResolver, }) if err != nil { return nil, err @@ -1641,6 +1672,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, br, err := lnwallet.NewBreachRetribution( channel, commitHeight, 0, nil, implCfg.AuxLeafStore, + implCfg.AuxContractResolver, ) if err != nil { return nil, 0, err @@ -1674,8 +1706,17 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return s.channelNotifier. SubscribeChannelEvents() }, - Signer: cc.Wallet.Cfg.Signer, - NewAddress: newSweepPkScriptGen(cc.Wallet), + Signer: cc.Wallet.Cfg.Signer, + NewAddress: func() ([]byte, error) { + addr, err := newSweepPkScriptGen( + cc.Wallet, netParams, + )().Unpack() + if err != nil { + return nil, err + } + + return addr.DeliveryAddress, nil + }, SecretKeyRing: s.cc.KeyRing, Dial: cfg.net.Dial, AuthDial: authDial, @@ -4125,6 +4166,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, AuxSigner: s.implCfg.AuxSigner, MsgRouter: s.implCfg.MsgRouter, AuxChanCloser: s.implCfg.AuxChanCloser, + AuxResolver: s.implCfg.AuxContractResolver, } copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed()) @@ -4944,18 +4986,34 @@ func (s *server) SendCustomMessage(peerPub [33]byte, msgType lnwire.MessageType, // Specifically, the script generated is a version 0, pay-to-witness-pubkey-hash // (p2wkh) output. func newSweepPkScriptGen( - wallet lnwallet.WalletController) func() ([]byte, error) { + wallet lnwallet.WalletController, + netParams *chaincfg.Params) func() fn.Result[lnwallet.AddrWithKey] { - return func() ([]byte, error) { + return func() fn.Result[lnwallet.AddrWithKey] { sweepAddr, err := wallet.NewAddress( lnwallet.TaprootPubkey, false, lnwallet.DefaultAccountName, ) if err != nil { - return nil, err + return fn.Err[lnwallet.AddrWithKey](err) } - return txscript.PayToAddrScript(sweepAddr) + addr, err := txscript.PayToAddrScript(sweepAddr) + if err != nil { + return fn.Err[lnwallet.AddrWithKey](err) + } + + internalKeyDesc, err := lnwallet.InternalKeyForAddr( + wallet, netParams, addr, + ) + if err != nil { + return fn.Err[lnwallet.AddrWithKey](err) + } + + return fn.Ok(lnwallet.AddrWithKey{ + DeliveryAddress: addr, + InternalKey: internalKeyDesc, + }) } } diff --git a/sweep/aggregator.go b/sweep/aggregator.go index 6028adca3..cd809e81a 100644 --- a/sweep/aggregator.go +++ b/sweep/aggregator.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" @@ -31,6 +32,10 @@ type BudgetAggregator struct { // maxInputs specifies the maximum number of inputs allowed in a single // sweep tx. maxInputs uint32 + + // auxSweeper is an optional interface that can be used to modify the + // way sweep transaction are generated. + auxSweeper fn.Option[AuxSweeper] } // Compile-time constraint to ensure BudgetAggregator implements UtxoAggregator. @@ -38,11 +43,12 @@ var _ UtxoAggregator = (*BudgetAggregator)(nil) // NewBudgetAggregator creates a new instance of a BudgetAggregator. func NewBudgetAggregator(estimator chainfee.Estimator, - maxInputs uint32) *BudgetAggregator { + maxInputs uint32, auxSweeper fn.Option[AuxSweeper]) *BudgetAggregator { return &BudgetAggregator{ - estimator: estimator, - maxInputs: maxInputs, + estimator: estimator, + maxInputs: maxInputs, + auxSweeper: auxSweeper, } } @@ -159,7 +165,7 @@ func (b *BudgetAggregator) createInputSets(inputs []SweeperInput, // Create an InputSet using the max allowed number of inputs. set, err := NewBudgetInputSet( - currentInputs, deadlineHeight, + currentInputs, deadlineHeight, b.auxSweeper, ) if err != nil { log.Errorf("unable to create input set: %v", err) @@ -173,7 +179,7 @@ func (b *BudgetAggregator) createInputSets(inputs []SweeperInput, // Create an InputSet from the remaining inputs. if len(remainingInputs) > 0 { set, err := NewBudgetInputSet( - remainingInputs, deadlineHeight, + remainingInputs, deadlineHeight, b.auxSweeper, ) if err != nil { log.Errorf("unable to create input set: %v", err) diff --git a/sweep/aggregator_test.go b/sweep/aggregator_test.go index a32c849a0..6df0d73fa 100644 --- a/sweep/aggregator_test.go +++ b/sweep/aggregator_test.go @@ -150,7 +150,7 @@ func TestBudgetAggregatorFilterInputs(t *testing.T) { // Init the budget aggregator with the mocked estimator and zero max // num of inputs. - b := NewBudgetAggregator(estimator, 0) + b := NewBudgetAggregator(estimator, 0, fn.None[AuxSweeper]()) // Call the method under test. result := b.filterInputs(inputs) @@ -214,7 +214,7 @@ func TestBudgetAggregatorSortInputs(t *testing.T) { } // Init the budget aggregator with zero max num of inputs. - b := NewBudgetAggregator(nil, 0) + b := NewBudgetAggregator(nil, 0, fn.None[AuxSweeper]()) // Call the method under test. result := b.sortInputs(inputs) @@ -279,7 +279,7 @@ func TestBudgetAggregatorCreateInputSets(t *testing.T) { } // Create a budget aggregator with max number of inputs set to 2. - b := NewBudgetAggregator(nil, 2) + b := NewBudgetAggregator(nil, 2, fn.None[AuxSweeper]()) // Create test cases. testCases := []struct { @@ -540,7 +540,9 @@ func TestBudgetInputSetClusterInputs(t *testing.T) { } // Create a budget aggregator with a max number of inputs set to 100. - b := NewBudgetAggregator(estimator, DefaultMaxInputsPerTx) + b := NewBudgetAggregator( + estimator, DefaultMaxInputsPerTx, fn.None[AuxSweeper](), + ) // Call the method under test. result := b.ClusterInputs(inputs) diff --git a/sweep/fee_bumper.go b/sweep/fee_bumper.go index 1d3fa5aed..fd3dfba9e 100644 --- a/sweep/fee_bumper.go +++ b/sweep/fee_bumper.go @@ -20,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/tlv" ) var ( @@ -43,6 +44,19 @@ var ( ErrThirdPartySpent = errors.New("third party spent the output") ) +var ( + // dummyChangePkScript is a dummy tapscript change script that's used + // when we don't need a real address, just something that can be used + // for fee estimation. + dummyChangePkScript = []byte{ + 0x51, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } +) + // Bumper defines an interface that can be used by other subsystems for fee // bumping. type Bumper interface { @@ -110,7 +124,7 @@ type BumpRequest struct { DeadlineHeight int32 // DeliveryAddress is the script to send the change output to. - DeliveryAddress []byte + DeliveryAddress lnwallet.AddrWithKey // MaxFeeRate is the maximum fee rate that can be used for fee bumping. MaxFeeRate chainfee.SatPerKWeight @@ -118,6 +132,10 @@ type BumpRequest struct { // StartingFeeRate is an optional parameter that can be used to specify // the initial fee rate to use for the fee function. StartingFeeRate fn.Option[chainfee.SatPerKWeight] + + // ExtraTxOut tracks if this bump request has an optional set of extra + // outputs to add to the transaction. + ExtraTxOut fn.Option[SweepOutput] } // MaxFeeRateAllowed returns the maximum fee rate allowed for the given @@ -125,9 +143,34 @@ type BumpRequest struct { // compares it with the specified MaxFeeRate, and returns the smaller of the // two. func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error) { + // We'll want to know if we have any blobs, as we need to factor this + // into the max fee rate for this bump request. + hasBlobs := fn.Any(func(i input.Input) bool { + return fn.MapOptionZ( + i.ResolutionBlob(), func(b tlv.Blob) bool { + return len(b) > 0 + }, + ) + }, r.Inputs) + + sweepAddrs := [][]byte{ + r.DeliveryAddress.DeliveryAddress, + } + + // If we have blobs, then we'll add an extra sweep addr for the size + // estimate below. We know that these blobs will also always be based on + // p2tr addrs. + if hasBlobs { + // We need to pass in a real address, so we'll use a dummy + // tapscript change script that's used elsewhere for tests. + sweepAddrs = append(sweepAddrs, dummyChangePkScript) + } + // Get the size of the sweep tx, which will be used to calculate the // budget fee rate. - size, err := calcSweepTxWeight(r.Inputs, r.DeliveryAddress) + size, err := calcSweepTxWeight( + r.Inputs, sweepAddrs, + ) if err != nil { return 0, err } @@ -155,7 +198,7 @@ func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error) { // calcSweepTxWeight calculates the weight of the sweep tx. It assumes a // sweeping tx always has a single output(change). func calcSweepTxWeight(inputs []input.Input, - outputPkScript []byte) (lntypes.WeightUnit, error) { + outputPkScript [][]byte) (lntypes.WeightUnit, error) { // Use a const fee rate as we only use the weight estimator to // calculate the size. @@ -248,6 +291,10 @@ type TxPublisherConfig struct { // Notifier is used to monitor the confirmation status of the tx. Notifier chainntnfs.ChainNotifier + + // AuxSweeper is an optional interface that can be used to modify the + // way sweep transaction are generated. + AuxSweeper fn.Option[AuxSweeper] } // TxPublisher is an implementation of the Bumper interface. It utilizes the @@ -401,16 +448,18 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest, for { // Create a new tx with the given fee rate and check its // mempool acceptance. - tx, fee, err := t.createAndCheckTx(req, f) + sweepCtx, err := t.createAndCheckTx(req, f) switch { case err == nil: // The tx is valid, return the request ID. - requestID := t.storeRecord(tx, req, f, fee) + requestID := t.storeRecord( + sweepCtx.tx, req, f, sweepCtx.fee, + ) log.Infof("Created tx %v for %v inputs: feerate=%v, "+ - "fee=%v, inputs=%v", tx.TxHash(), - len(req.Inputs), f.FeeRate(), fee, + "fee=%v, inputs=%v", sweepCtx.tx.TxHash(), + len(req.Inputs), f.FeeRate(), sweepCtx.fee, inputTypeSummary(req.Inputs)) return requestID, nil @@ -421,8 +470,8 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest, // We should at least start with a feerate above the // mempool min feerate, so if we get this error, it // means something is wrong earlier in the pipeline. - log.Errorf("Current fee=%v, feerate=%v, %v", fee, - f.FeeRate(), err) + log.Errorf("Current fee=%v, feerate=%v, %v", + sweepCtx.fee, f.FeeRate(), err) fallthrough @@ -434,8 +483,8 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest, // increased or maxed out. for !increased { log.Debugf("Increasing fee for next round, "+ - "current fee=%v, feerate=%v", fee, - f.FeeRate()) + "current fee=%v, feerate=%v", + sweepCtx.fee, f.FeeRate()) // If the fee function tells us that we have // used up the budget, we will return an error @@ -484,30 +533,34 @@ func (t *TxPublisher) storeRecord(tx *wire.MsgTx, req *BumpRequest, // script, and the fee rate. In addition, it validates the tx's mempool // acceptance before returning a tx that can be published directly, along with // its fee. -func (t *TxPublisher) createAndCheckTx(req *BumpRequest, f FeeFunction) ( - *wire.MsgTx, btcutil.Amount, error) { +func (t *TxPublisher) createAndCheckTx(req *BumpRequest, + f FeeFunction) (*sweepTxCtx, error) { // Create the sweep tx with max fee rate of 0 as the fee function // guarantees the fee rate used here won't exceed the max fee rate. - tx, fee, err := t.createSweepTx( + sweepCtx, err := t.createSweepTx( req.Inputs, req.DeliveryAddress, f.FeeRate(), ) if err != nil { - return nil, fee, fmt.Errorf("create sweep tx: %w", err) + return sweepCtx, fmt.Errorf("create sweep tx: %w", err) } // Sanity check the budget still covers the fee. - if fee > req.Budget { - return nil, fee, fmt.Errorf("%w: budget=%v, fee=%v", - ErrNotEnoughBudget, req.Budget, fee) + if sweepCtx.fee > req.Budget { + return sweepCtx, fmt.Errorf("%w: budget=%v, fee=%v", + ErrNotEnoughBudget, req.Budget, sweepCtx.fee) } + // If we had an extra txOut, then we'll update the result to include + // it. + req.ExtraTxOut = sweepCtx.extraTxOut + // Validate the tx's mempool acceptance. - err = t.cfg.Wallet.CheckMempoolAcceptance(tx) + err = t.cfg.Wallet.CheckMempoolAcceptance(sweepCtx.tx) // Exit early if the tx is valid. if err == nil { - return tx, fee, nil + return sweepCtx, nil } // Print an error log if the chain backend doesn't support the mempool @@ -515,18 +568,18 @@ func (t *TxPublisher) createAndCheckTx(req *BumpRequest, f FeeFunction) ( if errors.Is(err, rpcclient.ErrBackendVersion) { log.Errorf("TestMempoolAccept not supported by backend, " + "consider upgrading it to a newer version") - return tx, fee, nil + return sweepCtx, nil } // We are running on a backend that doesn't implement the RPC // testmempoolaccept, eg, neutrino, so we'll skip the check. if errors.Is(err, chain.ErrUnimplemented) { log.Debug("Skipped testmempoolaccept due to not implemented") - return tx, fee, nil + return sweepCtx, nil } - return nil, fee, fmt.Errorf("tx=%v failed mempool check: %w", - tx.TxHash(), err) + return sweepCtx, fmt.Errorf("tx=%v failed mempool check: %w", + sweepCtx.tx.TxHash(), err) } // broadcast takes a monitored tx and publishes it to the network. Prior to the @@ -547,6 +600,15 @@ func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) { log.Debugf("Publishing sweep tx %v, num_inputs=%v, height=%v", txid, len(tx.TxIn), t.currentHeight.Load()) + // Before we go to broadcast, we'll notify the aux sweeper, if it's + // present of this new broadcast attempt. + err := fn.MapOptionZ(t.cfg.AuxSweeper, func(aux AuxSweeper) error { + return aux.NotifyBroadcast(record.req, tx, record.fee) + }) + if err != nil { + return nil, fmt.Errorf("unable to notify aux sweeper: %w", err) + } + // Set the event, and change it to TxFailed if the wallet fails to // publish it. event := TxPublished @@ -554,7 +616,7 @@ func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) { // Publish the sweeping tx with customized label. If the publish fails, // this error will be saved in the `BumpResult` and it will be removed // from being monitored. - err := t.cfg.Wallet.PublishTransaction( + err = t.cfg.Wallet.PublishTransaction( tx, labels.MakeLabel(labels.LabelTypeSweepTransaction, nil), ) if err != nil { @@ -933,7 +995,7 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64, // NOTE: The fee function is expected to have increased its returned // fee rate after calling the SkipFeeBump method. So we can use it // directly here. - tx, fee, err := t.createAndCheckTx(r.req, r.feeFunction) + sweepCtx, err := t.createAndCheckTx(r.req, r.feeFunction) // If the error is fee related, we will return no error and let the fee // bumper retry it at next block. @@ -980,17 +1042,17 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64, // The tx has been created without any errors, we now register a new // record by overwriting the same requestID. t.records.Store(requestID, &monitorRecord{ - tx: tx, + tx: sweepCtx.tx, req: r.req, feeFunction: r.feeFunction, - fee: fee, + fee: sweepCtx.fee, }) // Attempt to broadcast this new tx. result, err := t.broadcast(requestID) if err != nil { log.Infof("Failed to broadcast replacement tx %v: %v", - tx.TxHash(), err) + sweepCtx.tx.TxHash(), err) return fn.None[BumpResult]() } @@ -1016,7 +1078,8 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64, return fn.Some(*result) } - log.Infof("Replaced tx=%v with new tx=%v", oldTx.TxHash(), tx.TxHash()) + log.Infof("Replaced tx=%v with new tx=%v", oldTx.TxHash(), + sweepCtx.tx.TxHash()) // Otherwise, it's a successful RBF, set the event and return. result.Event = TxReplaced @@ -1129,17 +1192,28 @@ func calcCurrentConfTarget(currentHeight, deadline int32) uint32 { return confTarget } +// sweepTxCtx houses a sweep transaction with additional context. +type sweepTxCtx struct { + tx *wire.MsgTx + + fee btcutil.Amount + + extraTxOut fn.Option[SweepOutput] +} + // createSweepTx creates a sweeping tx based on the given inputs, change // address and fee rate. -func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, - feeRate chainfee.SatPerKWeight) (*wire.MsgTx, btcutil.Amount, error) { +func (t *TxPublisher) createSweepTx(inputs []input.Input, + changePkScript lnwallet.AddrWithKey, + feeRate chainfee.SatPerKWeight) (*sweepTxCtx, error) { // Validate and calculate the fee and change amount. - txFee, changeAmtOpt, locktimeOpt, err := prepareSweepTx( + txFee, changeOutputsOpt, locktimeOpt, err := prepareSweepTx( inputs, changePkScript, feeRate, t.currentHeight.Load(), + t.cfg.AuxSweeper, ) if err != nil { - return nil, 0, err + return nil, err } var ( @@ -1182,12 +1256,12 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, }) } - // If there's a change amount, add it to the transaction. - changeAmtOpt.WhenSome(func(changeAmt btcutil.Amount) { - sweepTx.AddTxOut(&wire.TxOut{ - PkScript: changePkScript, - Value: int64(changeAmt), - }) + // If we have change outputs to add, then add it the sweep transaction + // here. + changeOutputsOpt.WhenSome(func(changeOuts []SweepOutput) { + for i := range changeOuts { + sweepTx.AddTxOut(&changeOuts[i].TxOut) + } }) // We'll default to using the current block height as locktime, if none @@ -1196,7 +1270,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, prevInputFetcher, err := input.MultiPrevOutFetcher(inputs) if err != nil { - return nil, 0, fmt.Errorf("error creating prev input fetcher "+ + return nil, fmt.Errorf("error creating prev input fetcher "+ "for hash cache: %v", err) } hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher) @@ -1224,35 +1298,87 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, for idx, inp := range idxs { if err := addInputScript(idx, inp); err != nil { - return nil, 0, err + return nil, err } } log.Debugf("Created sweep tx %v for inputs:\n%v", sweepTx.TxHash(), inputTypeSummary(inputs)) - return sweepTx, txFee, nil + // Try to locate the extra change output, though there might be None. + extraTxOut := fn.MapOption( + func(sweepOuts []SweepOutput) fn.Option[SweepOutput] { + for _, sweepOut := range sweepOuts { + if !sweepOut.IsExtra { + continue + } + + // If we sweep outputs of a custom channel, the + // custom leaves in those outputs will be merged + // into a single output, even if we sweep + // multiple outputs (e.g. to_remote and breached + // to_local of a breached channel) at the same + // time. So there will only ever be one extra + // output. + log.Debugf("Sweep produced extra_sweep_out=%v", + lnutils.SpewLogClosure(sweepOut)) + + return fn.Some(sweepOut) + } + + return fn.None[SweepOutput]() + }, + )(changeOutputsOpt) + + return &sweepTxCtx{ + tx: sweepTx, + fee: txFee, + extraTxOut: fn.FlattenOption(extraTxOut), + }, nil } -// prepareSweepTx returns the tx fee, an optional change amount and an optional -// locktime after a series of validations: +// prepareSweepTx returns the tx fee, a set of optional change outputs and an +// optional locktime after a series of validations: // 1. check the locktime has been reached. // 2. check the locktimes are the same. // 3. check the inputs cover the outputs. // // NOTE: if the change amount is below dust, it will be added to the tx fee. -func prepareSweepTx(inputs []input.Input, changePkScript []byte, - feeRate chainfee.SatPerKWeight, currentHeight int32) ( - btcutil.Amount, fn.Option[btcutil.Amount], fn.Option[int32], error) { +func prepareSweepTx(inputs []input.Input, changePkScript lnwallet.AddrWithKey, + feeRate chainfee.SatPerKWeight, currentHeight int32, + auxSweeper fn.Option[AuxSweeper]) ( + btcutil.Amount, fn.Option[[]SweepOutput], fn.Option[int32], error) { - noChange := fn.None[btcutil.Amount]() + noChange := fn.None[[]SweepOutput]() noLocktime := fn.None[int32]() + // Given the set of inputs we have, if we have an aux sweeper, then + // we'll attempt to see if we have any other change outputs we'll need + // to add to the sweep transaction. + changePkScripts := [][]byte{changePkScript.DeliveryAddress} + + var extraChangeOut fn.Option[SweepOutput] + err := fn.MapOptionZ( + auxSweeper, func(aux AuxSweeper) error { + extraOut := aux.DeriveSweepAddr(inputs, changePkScript) + if err := extraOut.Err(); err != nil { + return err + } + + extraChangeOut = extraOut.LeftToOption() + + return nil + }, + ) + if err != nil { + return 0, noChange, noLocktime, err + } + // Creating a weight estimator with nil outputs and zero max fee rate. // We don't allow adding customized outputs in the sweeping tx, and the // fee rate is already being managed before we get here. inputs, estimator, err := getWeightEstimate( - inputs, nil, feeRate, 0, changePkScript, + inputs, nil, feeRate, 0, changePkScripts, ) if err != nil { return 0, noChange, noLocktime, err @@ -1270,6 +1396,12 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte, requiredOutput btcutil.Amount ) + // If we have an extra change output, then we'll add it as a required + // output amt. + extraChangeOut.WhenSome(func(o SweepOutput) { + requiredOutput += btcutil.Amount(o.Value) + }) + // Go through each input and check if the required lock times have // reached and are the same. for _, o := range inputs { @@ -1316,14 +1448,22 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte, // The value remaining after the required output and fees is the // change output. changeAmt := totalInput - requiredOutput - txFee - changeAmtOpt := fn.Some(changeAmt) + changeOuts := make([]SweepOutput, 0, 2) + + extraChangeOut.WhenSome(func(o SweepOutput) { + changeOuts = append(changeOuts, o) + }) // We'll calculate the dust limit for the given changePkScript since it // is variable. - changeFloor := lnwallet.DustLimitForSize(len(changePkScript)) + changeFloor := lnwallet.DustLimitForSize( + len(changePkScript.DeliveryAddress), + ) - // If the change amount is dust, we'll move it into the fees. - if changeAmt < changeFloor { + switch { + // If the change amount is dust, we'll move it into the fees, and + // ignore it. + case changeAmt < changeFloor: log.Infof("Change amt %v below dustlimit %v, not adding "+ "change output", changeAmt, changeFloor) @@ -1338,8 +1478,16 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte, // The dust amount is added to the fee. txFee += changeAmt - // Set the change amount to none. - changeAmtOpt = fn.None[btcutil.Amount]() + // Otherwise, we'll actually recognize it as a change output. + default: + changeOuts = append(changeOuts, SweepOutput{ + TxOut: wire.TxOut{ + Value: int64(changeAmt), + PkScript: changePkScript.DeliveryAddress, + }, + IsExtra: false, + InternalKey: changePkScript.InternalKey, + }) } // Optionally set the locktime. @@ -1348,6 +1496,11 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte, locktimeOpt = noLocktime } + var changeOutsOpt fn.Option[[]SweepOutput] + if len(changeOuts) > 0 { + changeOutsOpt = fn.Some(changeOuts) + } + log.Debugf("Creating sweep tx for %v inputs (%s) using %v, "+ "tx_weight=%v, tx_fee=%v, locktime=%v, parents_count=%v, "+ "parents_fee=%v, parents_weight=%v, current_height=%v", @@ -1355,5 +1508,5 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte, estimator.weight(), txFee, locktimeOpt, len(estimator.parents), estimator.parentsFee, estimator.parentsWeight, currentHeight) - return txFee, changeAmtOpt, locktimeOpt, nil + return txFee, changeOutsOpt, locktimeOpt, nil } diff --git a/sweep/fee_bumper_test.go b/sweep/fee_bumper_test.go index 4c4a9519f..53b38607f 100644 --- a/sweep/fee_bumper_test.go +++ b/sweep/fee_bumper_test.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -21,12 +22,14 @@ import ( var ( // Create a taproot change script. - changePkScript = []byte{ - 0x51, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + changePkScript = lnwallet.AddrWithKey{ + DeliveryAddress: []byte{ + 0x51, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, } testInputCount atomic.Uint64 @@ -112,12 +115,16 @@ func TestCalcSweepTxWeight(t *testing.T) { inp := createTestInput(100, input.WitnessKeyHash) // Use a wrong change script to test the error case. - weight, err := calcSweepTxWeight([]input.Input{&inp}, []byte{0}) + weight, err := calcSweepTxWeight( + []input.Input{&inp}, [][]byte{{0x00}}, + ) require.Error(t, err) require.Zero(t, weight) // Use a correct change script to test the success case. - weight, err = calcSweepTxWeight([]input.Input{&inp}, changePkScript) + weight, err = calcSweepTxWeight( + []input.Input{&inp}, [][]byte{changePkScript.DeliveryAddress}, + ) require.NoError(t, err) // BaseTxSize 8 bytes @@ -137,7 +144,9 @@ func TestBumpRequestMaxFeeRateAllowed(t *testing.T) { inp := createTestInput(100, input.WitnessKeyHash) // The weight is 487. - weight, err := calcSweepTxWeight([]input.Input{&inp}, changePkScript) + weight, err := calcSweepTxWeight( + []input.Input{&inp}, [][]byte{changePkScript.DeliveryAddress}, + ) require.NoError(t, err) // Define a test budget and calculates its fee rate. @@ -154,7 +163,9 @@ func TestBumpRequestMaxFeeRateAllowed(t *testing.T) { // Use a wrong change script to test the error case. name: "error calc weight", req: &BumpRequest{ - DeliveryAddress: []byte{1}, + DeliveryAddress: lnwallet.AddrWithKey{ + DeliveryAddress: []byte{1}, + }, }, expectedMaxFeeRate: 0, expectedErr: true, @@ -239,7 +250,8 @@ func TestInitializeFeeFunction(t *testing.T) { // Create a publisher using the mocks. tp := NewTxPublisher(TxPublisherConfig{ - Estimator: estimator, + Estimator: estimator, + AuxSweeper: fn.Some[AuxSweeper](&MockAuxSweeper{}), }) // Create a test feerate. @@ -304,7 +316,9 @@ func TestStoreRecord(t *testing.T) { tx := &wire.MsgTx{} // Create a publisher using the mocks. - tp := NewTxPublisher(TxPublisherConfig{}) + tp := NewTxPublisher(TxPublisherConfig{ + AuxSweeper: fn.Some[AuxSweeper](&MockAuxSweeper{}), + }) // Get the current counter and check it's increased later. initialCounter := tp.requestCounter.Load() @@ -369,10 +383,11 @@ func createTestPublisher(t *testing.T) (*TxPublisher, *mockers) { // Create a publisher using the mocks. tp := NewTxPublisher(TxPublisherConfig{ - Estimator: m.estimator, - Signer: m.signer, - Wallet: m.wallet, - Notifier: m.notifier, + Estimator: m.estimator, + Signer: m.signer, + Wallet: m.wallet, + Notifier: m.notifier, + AuxSweeper: fn.Some[AuxSweeper](&MockAuxSweeper{}), }) return tp, m @@ -451,7 +466,7 @@ func TestCreateAndCheckTx(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Call the method under test. - _, _, err := tp.createAndCheckTx(tc.req, m.feeFunc) + _, err := tp.createAndCheckTx(tc.req, m.feeFunc) // Check the result is as expected. require.ErrorIs(t, err, tc.expectedErr) diff --git a/sweep/interface.go b/sweep/interface.go index 4b02f143c..acece3143 100644 --- a/sweep/interface.go +++ b/sweep/interface.go @@ -1,8 +1,12 @@ package sweep import ( + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -57,3 +61,37 @@ type Wallet interface { // service. BackEnd() string } + +// SweepOutput is an output used to sweep funds from a channel output. +type SweepOutput struct { //nolint:revive + wire.TxOut + + // IsExtra indicates whether this output is an extra output that was + // added by a party other than the sweeper. + IsExtra bool + + // InternalKey is the taproot internal key of the extra output. This is + // None, if this isn't a taproot output. + InternalKey fn.Option[keychain.KeyDescriptor] +} + +// AuxSweeper is used to enable a 3rd party to further shape the sweeping +// transaction by adding a set of extra outputs to the sweeping transaction. +type AuxSweeper interface { + // DeriveSweepAddr takes a set of inputs, and the change address we'd + // use to sweep them, and maybe results an extra sweep output that we + // should add to the sweeping transaction. + DeriveSweepAddr(inputs []input.Input, + change lnwallet.AddrWithKey) fn.Result[SweepOutput] + + // ExtraBudgetForInputs is used to determine the extra budget that + // should be allocated to sweep the given set of inputs. This can be + // used to add extra funds to the sweep transaction, for example to + // cover fees for additional outputs of custom channels. + ExtraBudgetForInputs(inputs []input.Input) fn.Result[btcutil.Amount] + + // NotifyBroadcast is used to notify external callers of the broadcast + // of a sweep transaction, generated by the passed BumpRequest. + NotifyBroadcast(req *BumpRequest, tx *wire.MsgTx, + totalFees btcutil.Amount) error +} diff --git a/sweep/mock_test.go b/sweep/mock_test.go index 605b8d14e..c623ca3c0 100644 --- a/sweep/mock_test.go +++ b/sweep/mock_test.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/mock" @@ -314,3 +315,44 @@ func (m *MockFeeFunction) IncreaseFeeRate(confTarget uint32) (bool, error) { return args.Bool(0), args.Error(1) } + +type MockAuxSweeper struct { + mock.Mock +} + +// DeriveSweepAddr takes a set of inputs, and the change address we'd +// use to sweep them, and maybe results an extra sweep output that we +// should add to the sweeping transaction. +func (m *MockAuxSweeper) DeriveSweepAddr(_ []input.Input, + _ lnwallet.AddrWithKey) fn.Result[SweepOutput] { + + return fn.Ok(SweepOutput{ + TxOut: wire.TxOut{ + Value: 123, + PkScript: changePkScript.DeliveryAddress, + }, + IsExtra: false, + InternalKey: fn.None[keychain.KeyDescriptor](), + }) +} + +// ExtraBudgetForInputs is used to determine the extra budget that +// should be allocated to sweep the given set of inputs. This can be +// used to add extra funds to the sweep transaction, for example to +// cover fees for additional outputs of custom channels. +func (m *MockAuxSweeper) ExtraBudgetForInputs( + _ []input.Input) fn.Result[btcutil.Amount] { + + args := m.Called() + amt := args.Get(0) + + return amt.(fn.Result[btcutil.Amount]) +} + +// NotifyBroadcast is used to notify external callers of the broadcast +// of a sweep transaction, generated by the passed BumpRequest. +func (*MockAuxSweeper) NotifyBroadcast(_ *BumpRequest, _ *wire.MsgTx, + _ btcutil.Amount) error { + + return nil +} diff --git a/sweep/sweeper.go b/sweep/sweeper.go index ff32d9489..6257faac1 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -298,7 +298,7 @@ type UtxoSweeper struct { // to sweep. inputs InputsMap - currentOutputScript []byte + currentOutputScript fn.Option[lnwallet.AddrWithKey] relayFeeRate chainfee.SatPerKWeight @@ -318,7 +318,7 @@ type UtxoSweeper struct { type UtxoSweeperConfig struct { // GenSweepScript generates a P2WKH script belonging to the wallet where // funds can be swept. - GenSweepScript func() ([]byte, error) + GenSweepScript func() fn.Result[lnwallet.AddrWithKey] // FeeEstimator is used when crafting sweep transactions to estimate // the necessary fee relative to the expected size of the sweep @@ -802,12 +802,19 @@ func (s *UtxoSweeper) signalResult(pi *SweeperInput, result Result) { // the tx. The output address is only marked as used if the publish succeeds. func (s *UtxoSweeper) sweep(set InputSet) error { // Generate an output script if there isn't an unused script available. - if s.currentOutputScript == nil { - pkScript, err := s.cfg.GenSweepScript() + if s.currentOutputScript.IsNone() { + addr, err := s.cfg.GenSweepScript().Unpack() if err != nil { return fmt.Errorf("gen sweep script: %w", err) } - s.currentOutputScript = pkScript + s.currentOutputScript = fn.Some(addr) + } + + sweepAddr, err := s.currentOutputScript.UnwrapOrErr( + fmt.Errorf("none sweep script"), + ) + if err != nil { + return err } // Create a fee bump request and ask the publisher to broadcast it. The @@ -817,7 +824,7 @@ func (s *UtxoSweeper) sweep(set InputSet) error { Inputs: set.Inputs(), Budget: set.Budget(), DeadlineHeight: set.DeadlineHeight(), - DeliveryAddress: s.currentOutputScript, + DeliveryAddress: sweepAddr, MaxFeeRate: s.cfg.MaxFeeRate.FeePerKWeight(), StartingFeeRate: set.StartingFeeRate(), // TODO(yy): pass the strategy here. @@ -1708,10 +1715,10 @@ func (s *UtxoSweeper) handleBumpEventTxPublished(r *BumpResult) error { log.Debugf("Published sweep tx %v, num_inputs=%v, height=%v", tx.TxHash(), len(tx.TxIn), s.currentHeight) - // If there's no error, remove the output script. Otherwise - // keep it so that it can be reused for the next transaction - // and causes no address inflation. - s.currentOutputScript = nil + // If there's no error, remove the output script. Otherwise keep it so + // that it can be reused for the next transaction and causes no address + // inflation. + s.currentOutputScript = fn.None[lnwallet.AddrWithKey]() return nil } diff --git a/sweep/sweeper_test.go b/sweep/sweeper_test.go index c8d9fc510..6d9c6c3d2 100644 --- a/sweep/sweeper_test.go +++ b/sweep/sweeper_test.go @@ -12,6 +12,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -667,8 +668,11 @@ func TestSweepPendingInputs(t *testing.T) { Wallet: wallet, Aggregator: aggregator, Publisher: publisher, - GenSweepScript: func() ([]byte, error) { - return testPubKey.SerializeCompressed(), nil + GenSweepScript: func() fn.Result[lnwallet.AddrWithKey] { + //nolint:lll + return fn.Ok(lnwallet.AddrWithKey{ + DeliveryAddress: testPubKey.SerializeCompressed(), + }) }, NoDeadlineConfTarget: uint32(DefaultDeadlineDelta), }) diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index 31f20b7db..3a95fff2f 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -111,17 +111,26 @@ type BudgetInputSet struct { // deadlineHeight is the height which the inputs in this set must be // confirmed by. deadlineHeight int32 + + // extraBudget is a value that should be allocated to sweep the given + // set of inputs. This can be used to add extra funds to the sweep + // transaction, for example to cover fees for additional outputs of + // custom channels. + extraBudget btcutil.Amount } // Compile-time constraint to ensure budgetInputSet implements InputSet. var _ InputSet = (*BudgetInputSet)(nil) +// errEmptyInputs is returned when the input slice is empty. +var errEmptyInputs = fmt.Errorf("inputs slice is empty") + // validateInputs is used when creating new BudgetInputSet to ensure there are // no duplicate inputs and they all share the same deadline heights, if set. func validateInputs(inputs []SweeperInput, deadlineHeight int32) error { // Sanity check the input slice to ensure it's non-empty. if len(inputs) == 0 { - return fmt.Errorf("inputs slice is empty") + return errEmptyInputs } // inputDeadline tracks the input's deadline height. It will be updated @@ -167,8 +176,8 @@ func validateInputs(inputs []SweeperInput, deadlineHeight int32) error { } // NewBudgetInputSet creates a new BudgetInputSet. -func NewBudgetInputSet(inputs []SweeperInput, - deadlineHeight int32) (*BudgetInputSet, error) { +func NewBudgetInputSet(inputs []SweeperInput, deadlineHeight int32, + auxSweeper fn.Option[AuxSweeper]) (*BudgetInputSet, error) { // Validate the supplied inputs. if err := validateInputs(inputs, deadlineHeight); err != nil { @@ -186,9 +195,32 @@ func NewBudgetInputSet(inputs []SweeperInput, log.Tracef("Created %v", bi.String()) + // Attach an optional budget. This will be a no-op if the auxSweeper + // is not set. + if err := bi.attachExtraBudget(auxSweeper); err != nil { + return nil, err + } + return bi, nil } +// attachExtraBudget attaches an extra budget to the input set, if the passed +// aux sweeper is set. +func (b *BudgetInputSet) attachExtraBudget(s fn.Option[AuxSweeper]) error { + extraBudget, err := fn.MapOptionZ( + s, func(aux AuxSweeper) fn.Result[btcutil.Amount] { + return aux.ExtraBudgetForInputs(b.Inputs()) + }, + ).Unpack() + if err != nil { + return err + } + + b.extraBudget = extraBudget + + return nil +} + // String returns a human-readable description of the input set. func (b *BudgetInputSet) String() string { inputsDesc := "" @@ -212,8 +244,10 @@ func (b *BudgetInputSet) addInput(input SweeperInput) { func (b *BudgetInputSet) NeedWalletInput() bool { var ( // budgetNeeded is the amount that needs to be covered from - // other inputs. - budgetNeeded btcutil.Amount + // other inputs. We start at the value of the extra budget, + // which might be needed for custom channels that add extra + // outputs. + budgetNeeded = b.extraBudget // budgetBorrowable is the amount that can be borrowed from // other inputs. diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index b6a87b378..8d0850b20 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -28,7 +28,9 @@ func TestNewBudgetInputSet(t *testing.T) { rt := require.New(t) // Pass an empty slice and expect an error. - set, err := NewBudgetInputSet([]SweeperInput{}, testHeight) + set, err := NewBudgetInputSet( + []SweeperInput{}, testHeight, fn.None[AuxSweeper](), + ) rt.ErrorContains(err, "inputs slice is empty") rt.Nil(set) @@ -66,23 +68,35 @@ func TestNewBudgetInputSet(t *testing.T) { } // Pass a slice of inputs with different deadline heights. - set, err = NewBudgetInputSet([]SweeperInput{input1, input2}, testHeight) + set, err = NewBudgetInputSet( + []SweeperInput{input1, input2}, testHeight, + fn.None[AuxSweeper](), + ) rt.ErrorContains(err, "input deadline height not matched") rt.Nil(set) // Pass a slice of inputs that only one input has the deadline height, // but it has a different value than the specified testHeight. - set, err = NewBudgetInputSet([]SweeperInput{input0, input2}, testHeight) + set, err = NewBudgetInputSet( + []SweeperInput{input0, input2}, testHeight, + fn.None[AuxSweeper](), + ) rt.ErrorContains(err, "input deadline height not matched") rt.Nil(set) // Pass a slice of inputs that are duplicates. - set, err = NewBudgetInputSet([]SweeperInput{input3, input3}, testHeight) + set, err = NewBudgetInputSet( + []SweeperInput{input3, input3}, testHeight, + fn.None[AuxSweeper](), + ) rt.ErrorContains(err, "duplicate inputs") rt.Nil(set) - // Pass a slice of inputs that only one input has the deadline height, - set, err = NewBudgetInputSet([]SweeperInput{input0, input3}, testHeight) + // Pass a slice of inputs that only one input has the deadline height. + set, err = NewBudgetInputSet( + []SweeperInput{input0, input3}, testHeight, + fn.None[AuxSweeper](), + ) rt.NoError(err) rt.NotNil(set) } @@ -102,7 +116,9 @@ func TestBudgetInputSetAddInput(t *testing.T) { } // Initialize an input set, which adds the above input. - set, err := NewBudgetInputSet([]SweeperInput{*pi}, testHeight) + set, err := NewBudgetInputSet( + []SweeperInput{*pi}, testHeight, fn.None[AuxSweeper](), + ) require.NoError(t, err) // Add the input to the set again. @@ -125,48 +141,55 @@ func TestNeedWalletInput(t *testing.T) { // Create a mock input that doesn't have required outputs. mockInput := &input.MockInput{} mockInput.On("RequiredTxOut").Return(nil) + mockInput.On("OutPoint").Return(wire.OutPoint{Hash: chainhash.Hash{1}}) defer mockInput.AssertExpectations(t) // Create a mock input that has required outputs. mockInputRequireOutput := &input.MockInput{} mockInputRequireOutput.On("RequiredTxOut").Return(&wire.TxOut{}) + mockInputRequireOutput.On("OutPoint").Return( + wire.OutPoint{Hash: chainhash.Hash{2}}, + ) defer mockInputRequireOutput.AssertExpectations(t) // We now create two pending inputs each has a budget of 100 satoshis. const budget = 100 // Create the pending input that doesn't have a required output. - piBudget := &SweeperInput{ + piBudget := SweeperInput{ Input: mockInput, params: Params{Budget: budget}, } // Create the pending input that has a required output. - piRequireOutput := &SweeperInput{ + piRequireOutput := SweeperInput{ Input: mockInputRequireOutput, params: Params{Budget: budget}, } testCases := []struct { name string - setupInputs func() []*SweeperInput + setupInputs func() []SweeperInput + extraBudget btcutil.Amount need bool + err error }{ { // When there are no pending inputs, we won't need a - // wallet input. Technically this should be an invalid + // wallet input. Technically this is be an invalid // state. name: "no inputs", - setupInputs: func() []*SweeperInput { + setupInputs: func() []SweeperInput { return nil }, need: false, + err: errEmptyInputs, }, { // When there's no required output, we don't need a // wallet input. name: "no required outputs", - setupInputs: func() []*SweeperInput { + setupInputs: func() []SweeperInput { // Create a sign descriptor to be used in the // pending input when calculating budgets can // be borrowed. @@ -177,15 +200,36 @@ func TestNeedWalletInput(t *testing.T) { } mockInput.On("SignDesc").Return(sd).Once() - return []*SweeperInput{piBudget} + return []SweeperInput{piBudget} }, need: false, }, + { + // When there's no required normal outputs, but an extra + // budget from custom channels, we will need a wallet + // input. + name: "no required normal outputs but extra budget", + setupInputs: func() []SweeperInput { + // Create a sign descriptor to be used in the + // pending input when calculating budgets can + // be borrowed. + sd := &input.SignDescriptor{ + Output: &wire.TxOut{ + Value: budget, + }, + } + mockInput.On("SignDesc").Return(sd).Once() + + return []SweeperInput{piBudget} + }, + extraBudget: 1000, + need: true, + }, { // When the output value cannot cover the budget, we // need a wallet input. name: "output value cannot cover budget", - setupInputs: func() []*SweeperInput { + setupInputs: func() []SweeperInput { // Create a sign descriptor to be used in the // pending input when calculating budgets can // be borrowed. @@ -194,8 +238,8 @@ func TestNeedWalletInput(t *testing.T) { Value: budget - 1, }, } - mockInput.On("SignDesc").Return(sd).Once() + mockInput.On("SignDesc").Return(sd).Once() // These two methods are only invoked when the // unit test is running with a logger. mockInput.On("OutPoint").Return( @@ -205,7 +249,7 @@ func TestNeedWalletInput(t *testing.T) { input.CommitmentAnchor, ).Maybe() - return []*SweeperInput{piBudget} + return []SweeperInput{piBudget} }, need: true, }, @@ -213,8 +257,8 @@ func TestNeedWalletInput(t *testing.T) { // When there's only inputs that require outputs, we // need wallet inputs. name: "only required outputs", - setupInputs: func() []*SweeperInput { - return []*SweeperInput{piRequireOutput} + setupInputs: func() []SweeperInput { + return []SweeperInput{piRequireOutput} }, need: true, }, @@ -223,7 +267,7 @@ func TestNeedWalletInput(t *testing.T) { // budget cannot cover the required, we need a wallet // input. name: "not enough budget to be borrowed", - setupInputs: func() []*SweeperInput { + setupInputs: func() []SweeperInput { // Create a sign descriptor to be used in the // pending input when calculating budgets can // be borrowed. @@ -237,7 +281,7 @@ func TestNeedWalletInput(t *testing.T) { } mockInput.On("SignDesc").Return(sd).Once() - return []*SweeperInput{ + return []SweeperInput{ piBudget, piRequireOutput, } }, @@ -248,7 +292,7 @@ func TestNeedWalletInput(t *testing.T) { // borrowed covers the required, we don't need wallet // inputs. name: "enough budget to be borrowed", - setupInputs: func() []*SweeperInput { + setupInputs: func() []SweeperInput { // Create a sign descriptor to be used in the // pending input when calculating budgets can // be borrowed. @@ -263,7 +307,7 @@ func TestNeedWalletInput(t *testing.T) { mockInput.On("SignDesc").Return(sd).Once() piBudget.Input = mockInput - return []*SweeperInput{ + return []SweeperInput{ piBudget, piRequireOutput, } }, @@ -276,12 +320,27 @@ func TestNeedWalletInput(t *testing.T) { // Setup testing inputs. inputs := tc.setupInputs() + // If an extra budget is set, then we'll update the mock + // to expect the extra budget. + mockAuxSweeper := &MockAuxSweeper{} + mockAuxSweeper.On("ExtraBudgetForInputs").Return( + fn.Ok(tc.extraBudget), + ) + // Initialize an input set, which adds the testing // inputs. - set := &BudgetInputSet{inputs: inputs} + set, err := NewBudgetInputSet( + inputs, 0, fn.Some[AuxSweeper](mockAuxSweeper), + ) + if err != nil { + require.ErrorIs(t, err, tc.err) + return + } result := set.NeedWalletInput() + require.Equal(t, tc.need, result) + mockAuxSweeper.AssertExpectations(t) }) } } @@ -434,7 +493,9 @@ func TestAddWalletInputSuccess(t *testing.T) { min, max).Return([]*lnwallet.Utxo{utxo, utxo}, nil).Once() // Initialize an input set with the pending input. - set, err := NewBudgetInputSet([]SweeperInput{*pi}, deadline) + set, err := NewBudgetInputSet( + []SweeperInput{*pi}, deadline, fn.None[AuxSweeper](), + ) require.NoError(t, err) // Add wallet inputs to the input set, which should give us an error as diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 30e11023e..993ee9e59 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -38,7 +38,7 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, signer input.Signer) (*wire.MsgTx, btcutil.Amount, error) { inputs, estimator, err := getWeightEstimate( - inputs, outputs, feeRate, maxFeeRate, changePkScript, + inputs, outputs, feeRate, maxFeeRate, [][]byte{changePkScript}, ) if err != nil { return nil, 0, err @@ -221,7 +221,7 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut, // Additionally, it returns counts for the number of csv and cltv inputs. func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, feeRate, maxFeeRate chainfee.SatPerKWeight, - outputPkScript []byte) ([]input.Input, *weightEstimator, error) { + outputPkScripts [][]byte) ([]input.Input, *weightEstimator, error) { // We initialize a weight estimator so we can accurately asses the // amount of fees we need to pay for this sweep transaction. @@ -237,31 +237,34 @@ func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut, // If there is any leftover change after paying to the given outputs // and required outputs, it will go to a single segwit p2wkh or p2tr - // address. This will be our change address, so ensure it contributes to - // our weight estimate. Note that if we have other outputs, we might end - // up creating a sweep tx without a change output. It is okay to add the - // change output to the weight estimate regardless, since the estimated - // fee will just be subtracted from this already dust output, and - // trimmed. - switch { - case txscript.IsPayToTaproot(outputPkScript): - weightEstimate.addP2TROutput() + // address. This will be our change address, so ensure it contributes + // to our weight estimate. Note that if we have other outputs, we might + // end up creating a sweep tx without a change output. It is okay to + // add the change output to the weight estimate regardless, since the + // estimated fee will just be subtracted from this already dust output, + // and trimmed. + for _, outputPkScript := range outputPkScripts { + switch { + case txscript.IsPayToTaproot(outputPkScript): + weightEstimate.addP2TROutput() - case txscript.IsPayToWitnessScriptHash(outputPkScript): - weightEstimate.addP2WSHOutput() + case txscript.IsPayToWitnessScriptHash(outputPkScript): + weightEstimate.addP2WSHOutput() - case txscript.IsPayToWitnessPubKeyHash(outputPkScript): - weightEstimate.addP2WKHOutput() + case txscript.IsPayToWitnessPubKeyHash(outputPkScript): + weightEstimate.addP2WKHOutput() - case txscript.IsPayToPubKeyHash(outputPkScript): - weightEstimate.estimator.AddP2PKHOutput() + case txscript.IsPayToPubKeyHash(outputPkScript): + weightEstimate.estimator.AddP2PKHOutput() - case txscript.IsPayToScriptHash(outputPkScript): - weightEstimate.estimator.AddP2SHOutput() + case txscript.IsPayToScriptHash(outputPkScript): + weightEstimate.estimator.AddP2SHOutput() - default: - // Unknown script type. - return nil, nil, errors.New("unknown script type") + default: + // Unknown script type. + return nil, nil, fmt.Errorf("unknown script "+ + "type: %x", outputPkScript) + } } // For each output, use its witness type to determine the estimate diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 48dcacd49..71477bd6e 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -51,7 +51,7 @@ func TestWeightEstimate(t *testing.T) { } _, estimator, err := getWeightEstimate( - inputs, nil, 0, 0, changePkScript, + inputs, nil, 0, 0, [][]byte{changePkScript}, ) require.NoError(t, err) @@ -153,7 +153,7 @@ func testUnknownScriptInner(t *testing.T, pkscript []byte, expectFail bool) { )) } - _, _, err := getWeightEstimate(inputs, nil, 0, 0, pkscript) + _, _, err := getWeightEstimate(inputs, nil, 0, 0, [][]byte{pkscript}) if expectFail { require.Error(t, err) } else {