diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2dc0ad163b..0f101f866d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ defaults: shell: bash env: - BITCOIN_VERSION: "27" + BITCOIN_VERSION: "28" TRANCHES: 8 @@ -31,7 +31,7 @@ env: # /dev.Dockerfile # /make/builder.Dockerfile # /.github/workflows/release.yml - GO_VERSION: 1.22.5 + GO_VERSION: 1.22.6 jobs: ######################## diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 702734cf8f..d7e932e158 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,12 +11,11 @@ defaults: env: # If you change this value, please change it in the following files as well: - # /.travis.yml # /Dockerfile # /dev.Dockerfile # /make/builder.Dockerfile # /.github/workflows/main.yml - GO_VERSION: 1.22.5 + GO_VERSION: 1.22.6 jobs: main: diff --git a/.gitignore b/.gitignore index a44a7b8f50..6be439ed7e 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ profile.tmp .DS_Store .vscode +*.code-workspace # Coverage test coverage.txt diff --git a/.golangci.yml b/.golangci.yml index f3de207048..8114945c6f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,18 +1,8 @@ run: - # timeout for analysis - deadline: 10m + go: "1.22.6" - # Skip autogenerated files for mobile and gRPC as well as copied code for - # internal use. - skip-files: - - "mobile\\/.*generated\\.go" - - "\\.pb\\.go$" - - "\\.pb\\.gw\\.go$" - - "internal\\/musig2v040" - - skip-dirs: - - channeldb/migration_01_to_11 - - channeldb/migration/lnwire21 + # Abort after 10 minutes. + timeout: 10m build-tags: - autopilotrpc @@ -57,7 +47,6 @@ linters-settings: - G306 # Poor file permissions used when writing to a new file. staticcheck: - go: "1.22.5" checks: ["-SA1019"] lll: @@ -133,25 +122,15 @@ linters: - gochecknoinits # Deprecated linters. See https://golangci-lint.run/usage/linters/. - - interfacer - - golint - - maligned - - scopelint - - exhaustivestruct - bodyclose - contextcheck - nilerr - noctx - rowserrcheck - sqlclosecheck - - structcheck - tparallel - unparam - wastedassign - - ifshort - - varcheck - - deadcode - - nosnakecase # Disable gofumpt as it has weird behavior regarding formatting multiple @@ -191,7 +170,7 @@ linters: - wrapcheck # Allow dynamic errors. - - goerr113 + - err113 # We use ErrXXX instead. - errname @@ -207,15 +186,41 @@ linters: # The linter is too aggressive and doesn't add much value since reviewers # will also catch magic numbers that make sense to extract. - gomnd + - mnd - # Some of the tests cannot be parallelized. On the other hand, we don't - # gain much performance with this check so we disable it for now until - # unit tests become our CI bottleneck. + # Some of the tests cannot be parallelized. On the other hand, we don't + # gain much performance with this check so we disable it for now until + # unit tests become our CI bottleneck. - paralleltest + # New linters that we haven't had time to address yet. + - testifylint + - perfsprint + - inamedparam + - copyloopvar + - tagalign + - protogetter + - revive + - depguard + - gosmopolitan + - intrange + + issues: # Only show newly introduced problems. - new-from-rev: 8c66353e4c02329abdacb5a8df29998035ec2e24 + new-from-rev: 77c7f776d5cbf9e147edc81d65ae5ba177a684e5 + + # Skip autogenerated files for mobile and gRPC as well as copied code for + # internal use. + skip-files: + - "mobile\\/.*generated\\.go" + - "\\.pb\\.go$" + - "\\.pb\\.gw\\.go$" + - "internal\\/musig2v040" + + skip-dirs: + - channeldb/migration_01_to_11 + - channeldb/migration/lnwire21 exclude-rules: # Exclude gosec from running for tests so that tests with weak randomness @@ -256,8 +261,8 @@ issues: - forbidigo - godot - # Allow fmt.Printf() in lncli. - - path: cmd/lncli/* + # Allow fmt.Printf() in commands. + - path: cmd/commands/* linters: - forbidigo diff --git a/Dockerfile b/Dockerfile index 683eff81a9..3a6f642b1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # /make/builder.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.22.5-alpine as builder +FROM golang:1.22.6-alpine as builder # Force Go to use the cgo based DNS resolver. This is required to ensure DNS # queries required to connect to linked containers succeed. diff --git a/Makefile b/Makefile index d7e726df1f..5568b0b20a 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ endif # GO_VERSION is the Go version used for the release build, docker files, and # GitHub Actions. This is the reference version for the project. All other Go # versions are checked against this version. -GO_VERSION = 1.22.5 +GO_VERSION = 1.22.6 GOBUILD := $(LOOPVARFIX) go build -v GOINSTALL := $(LOOPVARFIX) go install -v diff --git a/aliasmgr/aliasmgr.go b/aliasmgr/aliasmgr.go index f33eb391eb..f06cb53d79 100644 --- a/aliasmgr/aliasmgr.go +++ b/aliasmgr/aliasmgr.go @@ -5,11 +5,22 @@ import ( "fmt" "sync" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" + "golang.org/x/exp/maps" ) +// UpdateLinkAliases is a function type for a function that locates the active +// link that matches the given shortID and triggers an update based on the +// latest values of the alias manager. +type UpdateLinkAliases func(shortID lnwire.ShortChannelID) error + +// ScidAliasMap is a map from a base short channel ID to a set of alias short +// channel IDs. +type ScidAliasMap map[lnwire.ShortChannelID][]lnwire.ShortChannelID + var ( // aliasBucket stores aliases as keys and their base SCIDs as values. // This is used to populate the maps that the Manager uses. The keys @@ -47,17 +58,18 @@ var ( // operations. byteOrder = binary.BigEndian - // startBlockHeight is the starting block height of the alias range. - startingBlockHeight = 16_000_000 + // AliasStartBlockHeight is the starting block height of the alias + // range. + AliasStartBlockHeight uint32 = 16_000_000 - // endBlockHeight is the ending block height of the alias range. - endBlockHeight = 16_250_000 + // AliasEndBlockHeight is the ending block height of the alias range. + AliasEndBlockHeight uint32 = 16_250_000 // StartingAlias is the first alias ShortChannelID that will get // assigned by RequestAlias. The starting BlockHeight is chosen so that // legitimate SCIDs in integration tests aren't mistaken for an alias. StartingAlias = lnwire.ShortChannelID{ - BlockHeight: uint32(startingBlockHeight), + BlockHeight: AliasStartBlockHeight, TxIndex: 0, TxPosition: 0, } @@ -68,6 +80,10 @@ var ( // errNoPeerAlias is returned when the peer's alias for a given // channel is not found. errNoPeerAlias = fmt.Errorf("no peer alias found") + + // ErrAliasNotFound is returned when the alias is not found and can't + // be mapped to a base SCID. + ErrAliasNotFound = fmt.Errorf("alias not found") ) // Manager is a struct that handles aliases for LND. It has an underlying @@ -77,10 +93,14 @@ var ( type Manager struct { backend kvdb.Backend + // linkAliasUpdater is a function used by the alias manager to + // facilitate live update of aliases in other subsystems. + linkAliasUpdater UpdateLinkAliases + // baseToSet is a mapping from the "base" SCID to the set of aliases // for this channel. This mapping includes all channels that // negotiated the option-scid-alias feature bit. - baseToSet map[lnwire.ShortChannelID][]lnwire.ShortChannelID + baseToSet ScidAliasMap // aliasToBase is a mapping that maps all aliases for a given channel // to its base SCID. This is only used for channels that have @@ -98,9 +118,15 @@ type Manager struct { } // NewManager initializes an alias Manager from the passed database backend. -func NewManager(db kvdb.Backend) (*Manager, error) { - m := &Manager{backend: db} - m.baseToSet = make(map[lnwire.ShortChannelID][]lnwire.ShortChannelID) +func NewManager(db kvdb.Backend, linkAliasUpdater UpdateLinkAliases) (*Manager, + error) { + + m := &Manager{ + backend: db, + baseToSet: make(ScidAliasMap), + linkAliasUpdater: linkAliasUpdater, + } + m.aliasToBase = make(map[lnwire.ShortChannelID]lnwire.ShortChannelID) m.peerAlias = make(map[lnwire.ChannelID]lnwire.ShortChannelID) @@ -215,12 +241,22 @@ func (m *Manager) populateMaps() error { // AddLocalAlias adds a database mapping from the passed alias to the passed // base SCID. The gossip boolean marks whether or not to create a mapping // that the gossiper will use. It is set to false for the upgrade path where -// the feature-bit is toggled on and there are existing channels. +// the feature-bit is toggled on and there are existing channels. The linkUpdate +// flag is used to signal whether this function should also trigger an update +// on the htlcswitch scid alias maps. func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID, - gossip bool) error { + gossip, linkUpdate bool) error { + // We need to lock the manager for the whole duration of this method, + // except for the very last part where we call the link updater. In + // order for us to safely use a defer _and_ still be able to manually + // unlock, we use a sync.Once. m.Lock() - defer m.Unlock() + unlockOnce := sync.Once{} + unlock := func() { + unlockOnce.Do(m.Unlock) + } + defer unlock() err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error { // If the caller does not want to allow the alias to be used @@ -270,6 +306,18 @@ func (m *Manager) AddLocalAlias(alias, baseScid lnwire.ShortChannelID, m.aliasToBase[alias] = baseScid } + // We definitely need to unlock the Manager before calling the link + // updater. If we don't, we'll deadlock. We use a sync.Once to ensure + // that we only unlock once. + unlock() + + // Finally, we trigger a htlcswitch update if the flag is set, in order + // for any future htlc that references the added alias to be properly + // routed. + if linkUpdate { + return m.linkAliasUpdater(baseScid) + } + return nil } @@ -340,6 +388,74 @@ func (m *Manager) DeleteSixConfs(baseScid lnwire.ShortChannelID) error { return nil } +// DeleteLocalAlias removes a mapping from the database and the Manager's maps. +func (m *Manager) DeleteLocalAlias(alias, + baseScid lnwire.ShortChannelID) error { + + // We need to lock the manager for the whole duration of this method, + // except for the very last part where we call the link updater. In + // order for us to safely use a defer _and_ still be able to manually + // unlock, we use a sync.Once. + m.Lock() + unlockOnce := sync.Once{} + unlock := func() { + unlockOnce.Do(m.Unlock) + } + defer unlock() + + err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error { + aliasToBaseBucket, err := tx.CreateTopLevelBucket(aliasBucket) + if err != nil { + return err + } + + var aliasBytes [8]byte + byteOrder.PutUint64(aliasBytes[:], alias.ToUint64()) + + // If the user attempts to delete an alias that doesn't exist, + // we'll want to inform them about it and not just do nothing. + if aliasToBaseBucket.Get(aliasBytes[:]) == nil { + return ErrAliasNotFound + } + + return aliasToBaseBucket.Delete(aliasBytes[:]) + }, func() {}) + if err != nil { + return err + } + + // Now that the database state has been updated, we'll delete the + // mapping from the Manager's maps. + aliasSet, ok := m.baseToSet[baseScid] + if !ok { + return ErrAliasNotFound + } + + // We'll filter the alias set and remove the alias from it. + aliasSet = fn.Filter(func(a lnwire.ShortChannelID) bool { + return a.ToUint64() != alias.ToUint64() + }, aliasSet) + + // If the alias set is empty, we'll delete the base SCID from the + // baseToSet map. + if len(aliasSet) == 0 { + delete(m.baseToSet, baseScid) + } else { + m.baseToSet[baseScid] = aliasSet + } + + // Finally, we'll delete the aliasToBase mapping from the Manager's + // cache (but this is only set if we gossip the alias). + delete(m.aliasToBase, alias) + + // We definitely need to unlock the Manager before calling the link + // updater. If we don't, we'll deadlock. We use a sync.Once to ensure + // that we only unlock once. + unlock() + + return m.linkAliasUpdater(baseScid) +} + // PutPeerAlias stores the peer's alias SCID once we learn of it in the // channel_ready message. func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID, @@ -392,6 +508,19 @@ func (m *Manager) GetPeerAlias(chanID lnwire.ChannelID) (lnwire.ShortChannelID, func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) { var nextAlias lnwire.ShortChannelID + m.RLock() + defer m.RUnlock() + + // haveAlias returns true if the passed alias is already assigned to a + // channel in the baseToSet map. + haveAlias := func(maybeNextAlias lnwire.ShortChannelID) bool { + return fn.Any(func(aliasList []lnwire.ShortChannelID) bool { + return fn.Any(func(alias lnwire.ShortChannelID) bool { + return alias == maybeNextAlias + }, aliasList) + }, maps.Values(m.baseToSet)) + } + err := kvdb.Update(m.backend, func(tx kvdb.RwTx) error { bucket, err := tx.CreateTopLevelBucket(aliasAllocBucket) if err != nil { @@ -404,6 +533,29 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) { // StartingAlias to it. nextAlias = StartingAlias + // If the very first alias is already assigned, we'll + // keep incrementing until we find an unassigned alias. + // This is to avoid collision with custom added SCID + // aliases that fall into the same range as the ones we + // generate here monotonically. Those custom SCIDs are + // stored in a different bucket, but we can just check + // the in-memory map for simplicity. + for { + if !haveAlias(nextAlias) { + break + } + + nextAlias = getNextScid(nextAlias) + + // Abort if we've reached the end of the range. + if nextAlias.BlockHeight >= + AliasEndBlockHeight { + + return fmt.Errorf("range for custom " + + "aliases exhausted") + } + } + var scratch [8]byte byteOrder.PutUint64(scratch[:], nextAlias.ToUint64()) return bucket.Put(lastAliasKey, scratch[:]) @@ -418,6 +570,26 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) { ) nextAlias = getNextScid(lastScid) + // If the next alias is already assigned, we'll keep + // incrementing until we find an unassigned alias. This is to + // avoid collision with custom added SCID aliases that fall into + // the same range as the ones we generate here monotonically. + // Those custom SCIDs are stored in a different bucket, but we + // can just check the in-memory map for simplicity. + for { + if !haveAlias(nextAlias) { + break + } + + nextAlias = getNextScid(nextAlias) + + // Abort if we've reached the end of the range. + if nextAlias.BlockHeight >= AliasEndBlockHeight { + return fmt.Errorf("range for custom " + + "aliases exhausted") + } + } + var scratch [8]byte byteOrder.PutUint64(scratch[:], nextAlias.ToUint64()) return bucket.Put(lastAliasKey, scratch[:]) @@ -433,11 +605,11 @@ func (m *Manager) RequestAlias() (lnwire.ShortChannelID, error) { // ListAliases returns a carbon copy of baseToSet. This is used by the rpc // layer. -func (m *Manager) ListAliases() map[lnwire.ShortChannelID][]lnwire.ShortChannelID { +func (m *Manager) ListAliases() ScidAliasMap { m.RLock() defer m.RUnlock() - baseCopy := make(map[lnwire.ShortChannelID][]lnwire.ShortChannelID) + baseCopy := make(ScidAliasMap) for k, v := range m.baseToSet { setCopy := make([]lnwire.ShortChannelID, len(v)) @@ -496,10 +668,10 @@ func getNextScid(last lnwire.ShortChannelID) lnwire.ShortChannelID { // IsAlias returns true if the passed SCID is an alias. The function determines // this by looking at the BlockHeight. If the BlockHeight is greater than -// startingBlockHeight and less than endBlockHeight, then it is an alias +// AliasStartBlockHeight and less than AliasEndBlockHeight, then it is an alias // assigned by RequestAlias. These bounds only apply to aliases we generate. // Our peers are free to use any range they choose. func IsAlias(scid lnwire.ShortChannelID) bool { - return scid.BlockHeight >= uint32(startingBlockHeight) && - scid.BlockHeight < uint32(endBlockHeight) + return scid.BlockHeight >= AliasStartBlockHeight && + scid.BlockHeight < AliasEndBlockHeight } diff --git a/aliasmgr/aliasmgr_test.go b/aliasmgr/aliasmgr_test.go index 17159ed877..1844a82ef2 100644 --- a/aliasmgr/aliasmgr_test.go +++ b/aliasmgr/aliasmgr_test.go @@ -23,7 +23,11 @@ func TestAliasStorePeerAlias(t *testing.T) { require.NoError(t, err) defer db.Close() - aliasStore, err := NewManager(db) + linkUpdater := func(shortID lnwire.ShortChannelID) error { + return nil + } + + aliasStore, err := NewManager(db, linkUpdater) require.NoError(t, err) var chanID1 [32]byte @@ -52,7 +56,11 @@ func TestAliasStoreRequest(t *testing.T) { require.NoError(t, err) defer db.Close() - aliasStore, err := NewManager(db) + linkUpdater := func(shortID lnwire.ShortChannelID) error { + return nil + } + + aliasStore, err := NewManager(db, linkUpdater) require.NoError(t, err) // We'll assert that the very first alias we receive is StartingAlias. @@ -68,6 +76,118 @@ func TestAliasStoreRequest(t *testing.T) { require.Equal(t, nextAlias, alias2) } +// TestAliasLifecycle tests that the aliases can be created and deleted. +func TestAliasLifecycle(t *testing.T) { + t.Parallel() + + // Create the backend database and use this to create the aliasStore. + dbPath := filepath.Join(t.TempDir(), "testdb") + db, err := kvdb.Create( + kvdb.BoltBackendName, dbPath, true, kvdb.DefaultDBTimeout, + ) + require.NoError(t, err) + defer db.Close() + + updateChan := make(chan struct{}, 1) + + linkUpdater := func(shortID lnwire.ShortChannelID) error { + updateChan <- struct{}{} + return nil + } + + aliasStore, err := NewManager(db, linkUpdater) + require.NoError(t, err) + + const ( + base = uint64(123123123) + alias = uint64(456456456) + ) + + // Parse the aliases and base to short channel ID format. + baseScid := lnwire.NewShortChanIDFromInt(base) + aliasScid := lnwire.NewShortChanIDFromInt(alias) + aliasScid2 := lnwire.NewShortChanIDFromInt(alias + 1) + + // Add the first alias. + err = aliasStore.AddLocalAlias(aliasScid, baseScid, false, true) + require.NoError(t, err) + + // The link updater should be called. + <-updateChan + + // Query the aliases and verify the results. + aliasList := aliasStore.GetAliases(baseScid) + require.Len(t, aliasList, 1) + require.Contains(t, aliasList, aliasScid) + + // Add the second alias. + err = aliasStore.AddLocalAlias(aliasScid2, baseScid, false, true) + require.NoError(t, err) + + // The link updater should be called. + <-updateChan + + // Query the aliases and verify the results. + aliasList = aliasStore.GetAliases(baseScid) + require.Len(t, aliasList, 2) + require.Contains(t, aliasList, aliasScid) + require.Contains(t, aliasList, aliasScid2) + + // Delete the first alias. + err = aliasStore.DeleteLocalAlias(aliasScid, baseScid) + require.NoError(t, err) + + // The link updater should be called. + <-updateChan + + // We expect to get an error if we attempt to delete the same alias + // again. + err = aliasStore.DeleteLocalAlias(aliasScid, baseScid) + require.ErrorIs(t, err, ErrAliasNotFound) + + // The link updater should _not_ be called. + select { + case <-updateChan: + t.Fatal("link alias updater should not have been called") + default: + } + + // Query the aliases and verify that first one doesn't exist anymore. + aliasList = aliasStore.GetAliases(baseScid) + require.Len(t, aliasList, 1) + require.Contains(t, aliasList, aliasScid2) + require.NotContains(t, aliasList, aliasScid) + + // Delete the second alias. + err = aliasStore.DeleteLocalAlias(aliasScid2, baseScid) + require.NoError(t, err) + + // The link updater should be called. + <-updateChan + + // Query the aliases and verify that none exists. + aliasList = aliasStore.GetAliases(baseScid) + require.Len(t, aliasList, 0) + + // We now request an alias generated by the aliasStore. This should give + // the first from the pre-defined list of allocated aliases. + firstRequested, err := aliasStore.RequestAlias() + require.NoError(t, err) + require.Equal(t, StartingAlias, firstRequested) + + // We now manually add the next alias from the range as a custom alias. + secondAlias := getNextScid(firstRequested) + err = aliasStore.AddLocalAlias(secondAlias, baseScid, false, true) + require.NoError(t, err) + + // When we now request another alias from the allocation list, we expect + // the third one (tx position 2) to be returned. + thirdRequested, err := aliasStore.RequestAlias() + require.NoError(t, err) + require.Equal(t, getNextScid(secondAlias), thirdRequested) + require.EqualValues(t, 2, thirdRequested.TxPosition) +} + // TestGetNextScid tests that given a current lnwire.ShortChannelID, // getNextScid returns the expected alias to use next. func TestGetNextScid(t *testing.T) { @@ -80,7 +200,7 @@ func TestGetNextScid(t *testing.T) { name: "starting alias", current: StartingAlias, expected: lnwire.ShortChannelID{ - BlockHeight: uint32(startingBlockHeight), + BlockHeight: AliasStartBlockHeight, TxIndex: 0, TxPosition: 1, }, diff --git a/build/version.go b/build/version.go index fe2486b8fe..ef0627529b 100644 --- a/build/version.go +++ b/build/version.go @@ -43,11 +43,11 @@ const ( AppMinor uint = 18 // AppPatch defines the application patch for this binary. - AppPatch uint = 3 + AppPatch uint = 4 // AppPreRelease MUST only contain characters from semanticAlphabet per // the semantic versioning spec. - AppPreRelease = "beta" + AppPreRelease = "beta.rc1" ) func init() { diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go index 37e72fdee3..da1f8e08ad 100644 --- a/chainreg/chainregistry.go +++ b/chainreg/chainregistry.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -63,6 +64,14 @@ type Config struct { // state. ChanStateDB *channeldb.ChannelStateDB + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // AuxSigner is an optional signer that can be used to sign auxiliary + // leaves for certain custom channel types. + AuxSigner fn.Option[lnwallet.AuxSigner] + // BlockCache is the main cache for storing block information. BlockCache *blockcache.BlockCache diff --git a/chanacceptor/rpcacceptor.go b/chanacceptor/rpcacceptor.go index 779ba6f286..6ec7878954 100644 --- a/chanacceptor/rpcacceptor.go +++ b/chanacceptor/rpcacceptor.go @@ -356,6 +356,30 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error, ): commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ZeroConfRequired, + lnwire.ScidAliasRequired, + ): + commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY + + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ZeroConfRequired, + ): + commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY + + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + lnwire.ScidAliasRequired, + ): + commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY + + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootOverlayChansRequired, + ): + commitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY + case channelFeatures.OnlyContains( lnwire.StaticRemoteKeyRequired, ): diff --git a/channeldb/channel.go b/channeldb/channel.go index 4e3db2fc1d..c21716a456 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -226,27 +226,108 @@ const ( // A tlv type definition used to serialize an outpoint's indexStatus // for use in the outpoint index. indexStatusType tlv.Type = 0 +) - // A tlv type definition used to serialize and deserialize a KeyLocator - // from the database. - keyLocType tlv.Type = 1 +// openChannelTlvData houses the new data fields that are stored for each +// channel in a TLV stream within the root bucket. This is stored as a TLV +// stream appended to the existing hard-coded fields in the channel's root +// bucket. New fields being added to the channel state should be added here. +// +// NOTE: This struct is used for serialization purposes only and its fields +// should be accessed via the OpenChannel struct while in memory. +type openChannelTlvData struct { + // revokeKeyLoc is the key locator for the revocation key. + revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord] - // A tlv type used to serialize and deserialize the - // `InitialLocalBalance` field. - initialLocalBalanceType tlv.Type = 2 + // initialLocalBalance is the initial local balance of the channel. + initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64] - // A tlv type used to serialize and deserialize the - // `InitialRemoteBalance` field. - initialRemoteBalanceType tlv.Type = 3 + // initialRemoteBalance is the initial remote balance of the channel. + initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64] - // A tlv type definition used to serialize and deserialize the - // confirmed ShortChannelID for a zero-conf channel. - realScidType tlv.Type = 4 + // realScid is the real short channel ID of the channel corresponding to + // the on-chain outpoint. + realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID] - // A tlv type definition used to serialize and deserialize the - // Memo for the channel channel. - channelMemoType tlv.Type = 5 -) + // memo is an optional text field that gives context to the user about + // the channel. + memo tlv.OptionalRecordT[tlv.TlvType5, []byte] + + // tapscriptRoot is the optional Tapscript root the channel funding + // output commits to. + tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte] + + // customBlob is an optional TLV encoded blob of data representing + // custom channel funding information. + customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob] +} + +// encode serializes the openChannelTlvData to the given io.Writer. +func (c *openChannelTlvData) encode(w io.Writer) error { + tlvRecords := []tlv.Record{ + c.revokeKeyLoc.Record(), + c.initialLocalBalance.Record(), + c.initialRemoteBalance.Record(), + c.realScid.Record(), + } + c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) { + tlvRecords = append(tlvRecords, memo.Record()) + }) + c.tapscriptRoot.WhenSome( + func(root tlv.RecordT[tlv.TlvType6, [32]byte]) { + tlvRecords = append(tlvRecords, root.Record()) + }, + ) + c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType7, tlv.Blob]) { + tlvRecords = append(tlvRecords, blob.Record()) + }) + + // Create the tlv stream. + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// decode deserializes the openChannelTlvData from the given io.Reader. +func (c *openChannelTlvData) decode(r io.Reader) error { + memo := c.memo.Zero() + tapscriptRoot := c.tapscriptRoot.Zero() + blob := c.customBlob.Zero() + + // Create the tlv stream. + tlvStream, err := tlv.NewStream( + c.revokeKeyLoc.Record(), + c.initialLocalBalance.Record(), + c.initialRemoteBalance.Record(), + c.realScid.Record(), + memo.Record(), + tapscriptRoot.Record(), + blob.Record(), + ) + if err != nil { + return err + } + + tlvs, err := tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return err + } + + if _, ok := tlvs[memo.TlvType()]; ok { + c.memo = tlv.SomeRecordT(memo) + } + if _, ok := tlvs[tapscriptRoot.TlvType()]; ok { + c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot) + } + if _, ok := tlvs[c.customBlob.TlvType()]; ok { + c.customBlob = tlv.SomeRecordT(blob) + } + + return nil +} // indexStatus is an enum-like type that describes what state the // outpoint is in. Currently only two possible values. @@ -325,6 +406,11 @@ const ( // SimpleTaprootFeatureBit indicates that the simple-taproot-chans // feature bit was negotiated during the lifetime of the channel. SimpleTaprootFeatureBit ChannelType = 1 << 10 + + // TapscriptRootBit indicates that this is a MuSig2 channel with a top + // level tapscript commitment. This MUST be set along with the + // SimpleTaprootFeatureBit. + TapscriptRootBit ChannelType = 1 << 11 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -395,6 +481,12 @@ func (c ChannelType) IsTaproot() bool { return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit } +// HasTapscriptRoot returns true if the channel is using a top level tapscript +// root commitment. +func (c ChannelType) HasTapscriptRoot() bool { + return c&TapscriptRootBit == TapscriptRootBit +} + // ChannelStateBounds are the parameters from OpenChannel and AcceptChannel // that are responsible for providing bounds on the state space of the abstract // channel state. These values must be remembered for normal channel operation @@ -496,6 +588,53 @@ type ChannelConfig struct { HtlcBasePoint keychain.KeyDescriptor } +// commitTlvData stores all the optional data that may be stored as a TLV stream +// at the _end_ of the normal serialized commit on disk. +type commitTlvData struct { + // customBlob is a custom blob that may store extra data for custom + // channels. + customBlob tlv.OptionalRecordT[tlv.TlvType1, tlv.Blob] +} + +// encode encodes the aux data into the passed io.Writer. +func (c *commitTlvData) encode(w io.Writer) error { + var tlvRecords []tlv.Record + c.customBlob.WhenSome(func(blob tlv.RecordT[tlv.TlvType1, tlv.Blob]) { + tlvRecords = append(tlvRecords, blob.Record()) + }) + + // Create the tlv stream. + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// decode attempts to decode the aux data from the passed io.Reader. +func (c *commitTlvData) decode(r io.Reader) error { + blob := c.customBlob.Zero() + + tlvStream, err := tlv.NewStream( + blob.Record(), + ) + if err != nil { + return err + } + + tlvs, err := tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return err + } + + if _, ok := tlvs[c.customBlob.TlvType()]; ok { + c.customBlob = tlv.SomeRecordT(blob) + } + + return nil +} + // ChannelCommitment is a snapshot of the commitment state at a particular // point in the commitment chain. With each state transition, a snapshot of the // current state along with all non-settled HTLCs are recorded. These snapshots @@ -562,6 +701,11 @@ type ChannelCommitment struct { // able by us. CommitTx *wire.MsgTx + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This may track some custom + // specific state for this given commitment. + CustomBlob fn.Option[tlv.Blob] + // CommitSig is one half of the signature required to fully complete // the script for the commitment transaction above. This is the // signature signed by the remote party for our version of the @@ -571,9 +715,26 @@ type ChannelCommitment struct { // Htlcs is the set of HTLC's that are pending at this particular // commitment height. Htlcs []HTLC +} - // TODO(roasbeef): pending commit pointer? - // * lets just walk through +// amendTlvData updates the channel with the given auxiliary TLV data. +func (c *ChannelCommitment) amendTlvData(auxData commitTlvData) { + auxData.customBlob.WhenSomeV(func(blob tlv.Blob) { + c.CustomBlob = fn.Some(blob) + }) +} + +// extractTlvData creates a new commitTlvData from the given commitment. +func (c *ChannelCommitment) extractTlvData() commitTlvData { + var auxData commitTlvData + + c.CustomBlob.WhenSome(func(blob tlv.Blob) { + auxData.customBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType1](blob), + ) + }) + + return auxData } // ChannelStatus is a bit vector used to indicate whether an OpenChannel is in @@ -867,6 +1028,16 @@ type OpenChannel struct { // channel that will be useful to our future selves. Memo []byte + // TapscriptRoot is an optional tapscript root used to derive the MuSig2 + // funding output. + TapscriptRoot fn.Option[chainhash.Hash] + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This information is only created + // at channel funding time, and after wards is to be considered + // immutable. + CustomBlob fn.Option[tlv.Blob] + // TODO(roasbeef): eww Db *ChannelStateDB @@ -1025,6 +1196,64 @@ func (c *OpenChannel) SetBroadcastHeight(height uint32) { c.FundingBroadcastHeight = height } +// amendTlvData updates the channel with the given auxiliary TLV data. +func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) { + c.RevocationKeyLocator = auxData.revokeKeyLoc.Val.KeyLocator + c.InitialLocalBalance = lnwire.MilliSatoshi( + auxData.initialLocalBalance.Val, + ) + c.InitialRemoteBalance = lnwire.MilliSatoshi( + auxData.initialRemoteBalance.Val, + ) + c.confirmedScid = auxData.realScid.Val + + auxData.memo.WhenSomeV(func(memo []byte) { + c.Memo = memo + }) + auxData.tapscriptRoot.WhenSomeV(func(h [32]byte) { + c.TapscriptRoot = fn.Some[chainhash.Hash](h) + }) + auxData.customBlob.WhenSomeV(func(blob tlv.Blob) { + c.CustomBlob = fn.Some(blob) + }) +} + +// extractTlvData creates a new openChannelTlvData from the given channel. +func (c *OpenChannel) extractTlvData() openChannelTlvData { + auxData := openChannelTlvData{ + revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1]( + keyLocRecord{c.RevocationKeyLocator}, + ), + initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2]( + uint64(c.InitialLocalBalance), + ), + initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3]( + uint64(c.InitialRemoteBalance), + ), + realScid: tlv.NewRecordT[tlv.TlvType4]( + c.confirmedScid, + ), + } + + if len(c.Memo) != 0 { + auxData.memo = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5](c.Memo), + ) + } + c.TapscriptRoot.WhenSome(func(h chainhash.Hash) { + auxData.tapscriptRoot = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h), + ) + }) + c.CustomBlob.WhenSome(func(blob tlv.Blob) { + auxData.customBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType7](blob), + ) + }) + + return auxData +} + // Refresh updates the in-memory channel state using the latest state observed // on disk. func (c *OpenChannel) Refresh() error { @@ -2351,6 +2580,12 @@ type HTLC struct { // HTLC. It is stored in the ExtraData field, which is used to store // a TLV stream of additional information associated with the HTLC. BlindingPoint lnwire.BlindingPointRecord + + // CustomRecords is a set of custom TLV records that are associated with + // this HTLC. These records are used to store additional information + // about the HTLC that is not part of the standard HTLC fields. This + // field is encoded within the ExtraData field. + CustomRecords lnwire.CustomRecords } // serializeExtraData encodes a TLV stream of extra data to be stored with a @@ -2369,6 +2604,11 @@ func (h *HTLC) serializeExtraData() error { records = append(records, &b) }) + records, err := h.CustomRecords.ExtendRecordProducers(records) + if err != nil { + return err + } + return h.ExtraData.PackRecords(records...) } @@ -2390,8 +2630,19 @@ func (h *HTLC) deserializeExtraData() error { if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil { h.BlindingPoint = tlv.SomeRecordT(blindingPoint) + + // Remove the entry from the TLV map. Anything left in the map + // will be included in the custom records field. + delete(tlvMap, h.BlindingPoint.TlvType()) } + // Set the custom records field to the remaining TLV records. + customRecords, err := lnwire.NewCustomRecords(tlvMap) + if err != nil { + return err + } + h.CustomRecords = customRecords + return nil } @@ -2529,6 +2780,8 @@ func (h *HTLC) Copy() HTLC { copy(clone.Signature[:], h.Signature) copy(clone.RHash[:], h.RHash[:]) copy(clone.ExtraData, h.ExtraData) + clone.BlindingPoint = h.BlindingPoint + clone.CustomRecords = h.CustomRecords.Copy() return clone } @@ -2690,6 +2943,14 @@ func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl } } + // We'll also encode the commit aux data stream here. We do this here + // rather than above (at the call to serializeChanCommit), to ensure + // backwards compat for reads to existing non-custom channels. + auxData := diff.Commitment.extractTlvData() + if err := auxData.encode(w); err != nil { + return fmt.Errorf("unable to write aux data: %w", err) + } + return nil } @@ -2750,6 +3011,17 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) { } } + // As a final step, we'll read out any aux commit data that we have at + // the end of this byte stream. We do this here to ensure backward + // compatibility, as otherwise we risk erroneously reading into the + // wrong field. + var auxData commitTlvData + if err := auxData.decode(r); err != nil { + return nil, fmt.Errorf("unable to decode aux data: %w", err) + } + + d.Commitment.amendTlvData(auxData) + return &d, nil } @@ -3728,6 +4000,13 @@ func (c *OpenChannel) Snapshot() *ChannelSnapshot { }, } + localCommit.CustomBlob.WhenSome(func(blob tlv.Blob) { + blobCopy := make([]byte, len(blob)) + copy(blobCopy, blob) + + snapshot.ChannelCommitment.CustomBlob = fn.Some(blobCopy) + }) + // Copy over the current set of HTLCs to ensure the caller can't mutate // our internal state. snapshot.Htlcs = make([]HTLC, len(localCommit.Htlcs)) @@ -4030,32 +4309,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error { return err } - // Convert balance fields into uint64. - localBalance := uint64(channel.InitialLocalBalance) - remoteBalance := uint64(channel.InitialRemoteBalance) - - // Create the tlv stream. - tlvStream, err := tlv.NewStream( - // Write the RevocationKeyLocator as the first entry in a tlv - // stream. - MakeKeyLocRecord( - keyLocType, &channel.RevocationKeyLocator, - ), - tlv.MakePrimitiveRecord( - initialLocalBalanceType, &localBalance, - ), - tlv.MakePrimitiveRecord( - initialRemoteBalanceType, &remoteBalance, - ), - MakeScidRecord(realScidType, &channel.confirmedScid), - tlv.MakePrimitiveRecord(channelMemoType, &channel.Memo), - ) - if err != nil { - return err - } - - if err := tlvStream.Encode(&w); err != nil { - return err + auxData := channel.extractTlvData() + if err := auxData.encode(&w); err != nil { + return fmt.Errorf("unable to encode aux data: %w", err) } if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil { @@ -4142,6 +4398,12 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment, return err } + // Before we write to disk, we'll also write our aux data as well. + auxData := c.extractTlvData() + if err := auxData.encode(&b); err != nil { + return fmt.Errorf("unable to write aux data: %w", err) + } + return chanBucket.Put(commitKey, b.Bytes()) } @@ -4244,45 +4506,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error { } } - // Create balance fields in uint64, and Memo field as byte slice. - var ( - localBalance uint64 - remoteBalance uint64 - memo []byte - ) - - // Create the tlv stream. - tlvStream, err := tlv.NewStream( - // Write the RevocationKeyLocator as the first entry in a tlv - // stream. - MakeKeyLocRecord( - keyLocType, &channel.RevocationKeyLocator, - ), - tlv.MakePrimitiveRecord( - initialLocalBalanceType, &localBalance, - ), - tlv.MakePrimitiveRecord( - initialRemoteBalanceType, &remoteBalance, - ), - MakeScidRecord(realScidType, &channel.confirmedScid), - tlv.MakePrimitiveRecord(channelMemoType, &memo), - ) - if err != nil { - return err - } - - if err := tlvStream.Decode(r); err != nil { - return err + var auxData openChannelTlvData + if err := auxData.decode(r); err != nil { + return fmt.Errorf("unable to decode aux data: %w", err) } - // Attach the balance fields. - channel.InitialLocalBalance = lnwire.MilliSatoshi(localBalance) - channel.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance) - - // Attach the memo field if non-empty. - if len(memo) > 0 { - channel.Memo = memo - } + // Assign all the relevant fields from the aux data into the actual + // open channel. + channel.amendTlvData(auxData) channel.Packager = NewChannelPackager(channel.ShortChannelID) @@ -4318,7 +4549,9 @@ func deserializeChanCommit(r io.Reader) (ChannelCommitment, error) { return c, nil } -func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment, error) { +func fetchChanCommitment(chanBucket kvdb.RBucket, + local bool) (ChannelCommitment, error) { + var commitKey []byte if local { commitKey = append(chanCommitmentKey, byte(0x00)) @@ -4332,7 +4565,23 @@ func fetchChanCommitment(chanBucket kvdb.RBucket, local bool) (ChannelCommitment } r := bytes.NewReader(commitBytes) - return deserializeChanCommit(r) + chanCommit, err := deserializeChanCommit(r) + if err != nil { + return ChannelCommitment{}, fmt.Errorf("unable to decode "+ + "chan commit: %w", err) + } + + // We'll also check to see if we have any aux data stored as the end of + // the stream. + var auxData commitTlvData + if err := auxData.decode(r); err != nil { + return ChannelCommitment{}, fmt.Errorf("unable to decode "+ + "chan aux data: %w", err) + } + + chanCommit.amendTlvData(auxData) + + return chanCommit, nil } func fetchChanCommitments(chanBucket kvdb.RBucket, channel *OpenChannel) error { @@ -4440,6 +4689,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error { return chanBucket.Delete(frozenChanKey) } +// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the +// tlv.RecordProducer interface. +type keyLocRecord struct { + keychain.KeyLocator +} + +// Record creates a Record out of a KeyLocator using the passed Type and the +// EKeyLocator and DKeyLocator functions. The size will always be 8 as +// KeyFamily is uint32 and the Index is uint32. +// +// NOTE: This is part of the tlv.RecordProducer interface. +func (k *keyLocRecord) Record() tlv.Record { + // Note that we set the type here as zero, as when used with a + // tlv.RecordT, the type param will be used as the type. + return tlv.MakeStaticRecord( + 0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator, + ) +} + // EKeyLocator is an encoder for keychain.KeyLocator. func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error { if v, ok := val.(*keychain.KeyLocator); ok { @@ -4468,22 +4736,6 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8) } -// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed -// Type and the EKeyLocator and DKeyLocator functions. The size will always be -// 8 as KeyFamily is uint32 and the Index is uint32. -func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record { - return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator) -} - -// MakeScidRecord creates a Record out of a ShortChannelID using the passed -// Type and the EShortChannelID and DShortChannelID functions. The size will -// always be 8 for the ShortChannelID. -func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record { - return tlv.MakeStaticRecord( - typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID, - ) -} - // ShutdownInfo contains various info about the shutdown initiation of a // channel. type ShutdownInfo struct { diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index a7f3c1ebee..e92692201d 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -17,6 +17,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnmock" @@ -173,7 +174,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption { } // channelIDOption is an option which sets the short channel ID of the channel. -var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption { +func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption { return func(params *testChannelParams) { params.channel.ShortChannelID = chanID } @@ -326,6 +327,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { uniqueOutputIndex.Add(1) op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()} + var tapscriptRoot chainhash.Hash + copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32)) + return &OpenChannel{ ChanType: SingleFunderBit | FrozenBit, ChainHash: key, @@ -347,6 +351,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { FeePerKw: btcutil.Amount(5000), CommitTx: channels.TestFundingTx, CommitSig: bytes.Repeat([]byte{1}, 71), + CustomBlob: fn.Some([]byte{1, 2, 3}), }, RemoteCommitment: ChannelCommitment{ CommitHeight: 0, @@ -356,6 +361,7 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { FeePerKw: btcutil.Amount(5000), CommitTx: channels.TestFundingTx, CommitSig: bytes.Repeat([]byte{1}, 71), + CustomBlob: fn.Some([]byte{4, 5, 6}), }, NumConfsRequired: 4, RemoteCurrentRevocation: privKey.PubKey(), @@ -368,6 +374,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel { ThawHeight: uint32(defaultPendingHeight), InitialLocalBalance: lnwire.MilliSatoshi(9000), InitialRemoteBalance: lnwire.MilliSatoshi(3000), + Memo: []byte("test"), + TapscriptRoot: fn.Some(tapscriptRoot), + CustomBlob: fn.Some([]byte{1, 2, 3}), } } @@ -575,24 +584,32 @@ func assertCommitmentEqual(t *testing.T, a, b *ChannelCommitment) { func assertRevocationLogEntryEqual(t *testing.T, c *ChannelCommitment, r *RevocationLog) { + t.Helper() + // Check the common fields. require.EqualValues( - t, r.CommitTxHash, c.CommitTx.TxHash(), "CommitTx mismatch", + t, r.CommitTxHash.Val, c.CommitTx.TxHash(), "CommitTx mismatch", ) // Now check the common fields from the HTLCs. require.Equal(t, len(r.HTLCEntries), len(c.Htlcs), "HTLCs len mismatch") for i, rHtlc := range r.HTLCEntries { cHtlc := c.Htlcs[i] - require.Equal(t, rHtlc.RHash, cHtlc.RHash, "RHash mismatch") - require.Equal(t, rHtlc.Amt, cHtlc.Amt.ToSatoshis(), - "Amt mismatch") - require.Equal(t, rHtlc.RefundTimeout, cHtlc.RefundTimeout, - "RefundTimeout mismatch") - require.EqualValues(t, rHtlc.OutputIndex, cHtlc.OutputIndex, - "OutputIndex mismatch") - require.Equal(t, rHtlc.Incoming, cHtlc.Incoming, - "Incoming mismatch") + require.Equal(t, rHtlc.RHash.Val[:], cHtlc.RHash[:], "RHash") + require.Equal( + t, rHtlc.Amt.Val.Int(), cHtlc.Amt.ToSatoshis(), "Amt", + ) + require.Equal( + t, rHtlc.RefundTimeout.Val, cHtlc.RefundTimeout, + "RefundTimeout", + ) + require.EqualValues( + t, rHtlc.OutputIndex.Val, cHtlc.OutputIndex, + "OutputIndex", + ) + require.Equal( + t, rHtlc.Incoming.Val, cHtlc.Incoming, "Incoming", + ) } } @@ -657,6 +674,7 @@ func TestChannelStateTransition(t *testing.T) { CommitTx: newTx, CommitSig: newSig, Htlcs: htlcs, + CustomBlob: fn.Some([]byte{4, 5, 6}), } // First update the local node's broadcastable state and also add a @@ -694,9 +712,14 @@ func TestChannelStateTransition(t *testing.T) { // have been updated. updatedChannel, err := cdb.FetchOpenChannels(channel.IdentityPub) require.NoError(t, err, "unable to fetch updated channel") - assertCommitmentEqual(t, &commitment, &updatedChannel[0].LocalCommitment) + + assertCommitmentEqual( + t, &commitment, &updatedChannel[0].LocalCommitment, + ) + numDiskUpdates, err := updatedChannel[0].CommitmentHeight() require.NoError(t, err, "unable to read commitment height from disk") + if numDiskUpdates != uint64(commitment.CommitHeight) { t.Fatalf("num disk updates doesn't match: %v vs %v", numDiskUpdates, commitment.CommitHeight) @@ -799,10 +822,10 @@ func TestChannelStateTransition(t *testing.T) { // Check the output indexes are saved as expected. require.EqualValues( - t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex, + t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val, ) require.EqualValues( - t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex, + t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val, ) // The two deltas (the original vs the on-disk version) should @@ -844,10 +867,10 @@ func TestChannelStateTransition(t *testing.T) { // Check the output indexes are saved as expected. require.EqualValues( - t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex, + t, dummyLocalOutputIndex, diskPrevCommit.OurOutputIndex.Val, ) require.EqualValues( - t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex, + t, dummyRemoteOutIndex, diskPrevCommit.TheirOutputIndex.Val, ) assertRevocationLogEntryEqual(t, &oldRemoteCommit, prevCommit) @@ -1642,6 +1665,24 @@ func TestHTLCsExtraData(t *testing.T) { ), } + // Custom channel data htlc with a blinding point. + customDataHTLC := HTLC{ + Signature: testSig.Serialize(), + Incoming: false, + Amt: 10, + RHash: key, + RefundTimeout: 1, + OnionBlob: lnmock.MockOnion(), + BlindingPoint: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( + pubKey, + ), + ), + CustomRecords: map[uint64][]byte{ + uint64(lnwire.MinCustomRecordsTlvType + 3): {1, 2, 3}, + }, + } + testCases := []struct { name string htlcs []HTLC @@ -1663,6 +1704,7 @@ func TestHTLCsExtraData(t *testing.T) { mockHtlc, blindingPointHTLC, mockHtlc, + customDataHTLC, }, }, } diff --git a/channeldb/forwarding_package.go b/channeldb/forwarding_package.go index 4c447fc035..11c0099e75 100644 --- a/channeldb/forwarding_package.go +++ b/channeldb/forwarding_package.go @@ -286,6 +286,27 @@ func NewFwdPkg(source lnwire.ShortChannelID, height uint64, } } +// SourceRef is a convenience method that returns an AddRef to this forwarding +// package for the index in the argument. It is the caller's responsibility +// to ensure that the index is in bounds. +func (f *FwdPkg) SourceRef(i uint16) AddRef { + return AddRef{ + Height: f.Height, + Index: i, + } +} + +// DestRef is a convenience method that returns a SettleFailRef to this +// forwarding package for the index in the argument. It is the caller's +// responsibility to ensure that the index is in bounds. +func (f *FwdPkg) DestRef(i uint16) SettleFailRef { + return SettleFailRef{ + Source: f.Source, + Height: f.Height, + Index: i, + } +} + // ID returns an unique identifier for this package, used to ensure that sphinx // replay processing of this batch is idempotent. func (f *FwdPkg) ID() []byte { diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index f45bc307b2..89197a0a80 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -2382,7 +2382,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { methodsMu.Unlock() err := fn() - require.NoErrorf(t, err, fmt.Sprintf(name)) + require.NoErrorf(t, err, name) } }) } diff --git a/channeldb/migration_01_to_11/migration_11_invoices_test.go b/channeldb/migration_01_to_11/migration_11_invoices_test.go index 553a8cfeee..55c188bd95 100644 --- a/channeldb/migration_01_to_11/migration_11_invoices_test.go +++ b/channeldb/migration_01_to_11/migration_11_invoices_test.go @@ -2,7 +2,6 @@ package migration_01_to_11 import ( "bytes" - "fmt" "testing" "time" @@ -154,12 +153,7 @@ func signDigestCompact(hash []byte) ([]byte, error) { privKey, _ := btcec.PrivKeyFromBytes(testPrivKeyBytes) // ecdsa.SignCompact returns a pubkey-recoverable signature - sig, err := ecdsa.SignCompact(privKey, hash, isCompressedKey) - if err != nil { - return nil, fmt.Errorf("can't sign the hash: %w", err) - } - - return sig, nil + return ecdsa.SignCompact(privKey, hash, isCompressedKey), nil } // getPayReq creates a payment request for the given net. diff --git a/channeldb/models/channel_edge_info.go b/channeldb/models/channel_edge_info.go index 1afa2d6272..0f91e2bbec 100644 --- a/channeldb/models/channel_edge_info.go +++ b/channeldb/models/channel_edge_info.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" ) // ChannelEdgeInfo represents a fully authenticated channel along with all its @@ -62,6 +63,11 @@ type ChannelEdgeInfo struct { // the value output in the outpoint that created this channel. Capacity btcutil.Amount + // TapscriptRoot is the optional Merkle root of the tapscript tree if + // this channel is a taproot channel that also commits to a tapscript + // tree (custom channel). + TapscriptRoot fn.Option[chainhash.Hash] + // ExtraOpaqueData is the set of data that was appended to this // message, some of which we may not actually know how to iterate or // parse. By holding onto this data, we ensure that we're able to diff --git a/channeldb/payments.go b/channeldb/payments.go index 079fedf235..0ccb75774c 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -195,6 +195,11 @@ type PaymentCreationInfo struct { // PaymentRequest is the full payment request, if any. PaymentRequest []byte + + // FirstHopCustomRecords are the TLV records that are to be sent to the + // first hop of this payment. These records will be transmitted via the + // wire message only and therefore do not affect the onion payload size. + FirstHopCustomRecords lnwire.CustomRecords } // htlcBucketKey creates a composite key from prefix and id where the result is @@ -1010,10 +1015,21 @@ func serializePaymentCreationInfo(w io.Writer, c *PaymentCreationInfo) error { return err } + // Any remaining bytes are TLV encoded records. Currently, these are + // only the custom records provided by the user to be sent to the first + // hop. But this can easily be extended with further records by merging + // the records into a single TLV stream. + err := c.FirstHopCustomRecords.SerializeTo(w) + if err != nil { + return err + } + return nil } -func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) { +func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, + error) { + var scratch [8]byte c := &PaymentCreationInfo{} @@ -1046,6 +1062,15 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) { } c.PaymentRequest = payReq + // Any remaining bytes are TLV encoded records. Currently, these are + // only the custom records provided by the user to be sent to the first + // hop. But this can easily be extended with further records by merging + // the records into a single TLV stream. + c.FirstHopCustomRecords, err = lnwire.ParseCustomRecordsFrom(r) + if err != nil { + return nil, err + } + return c, nil } @@ -1071,6 +1096,25 @@ func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { return err } + // Merge the fixed/known records together with the custom records to + // serialize them as a single blob. We can't do this in SerializeRoute + // because we're in the middle of the byte stream there. We can only do + // TLV serialization at the end of the stream, since EOF is allowed for + // a stream if no more data is expected. + producers := []tlv.RecordProducer{ + &a.Route.FirstHopAmount, + } + tlvData, err := lnwire.MergeAndEncode( + producers, nil, a.Route.FirstHopWireCustomRecords, + ) + if err != nil { + return err + } + + if _, err := w.Write(tlvData); err != nil { + return err + } + return nil } @@ -1108,6 +1152,22 @@ func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { a.Hash = &hash + // Read any remaining data (if any) and parse it into the known records + // and custom records. + extraData, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + customRecords, _, _, err := lnwire.ParseAndExtractCustomRecords( + extraData, &a.Route.FirstHopAmount, + ) + if err != nil { + return nil, err + } + + a.Route.FirstHopWireCustomRecords = customRecords + return a, nil } @@ -1373,6 +1433,8 @@ func SerializeRoute(w io.Writer, r route.Route) error { } } + // Any new/extra TLV data is encoded in serializeHTLCAttemptInfo! + return nil } @@ -1406,5 +1468,7 @@ func DeserializeRoute(r io.Reader) (route.Route, error) { } rt.Hops = hops + // Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo! + return rt, nil } diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 769f4cc77f..0c3753e662 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -13,8 +13,10 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -108,7 +110,7 @@ func makeFakeInfo() (*PaymentCreationInfo, *HTLCAttemptInfo) { // Use single second precision to avoid false positive test // failures due to the monotonic time component. CreationTime: time.Unix(time.Now().Unix(), 0), - PaymentRequest: []byte(""), + PaymentRequest: []byte("test"), } a := NewHtlcAttempt( @@ -124,51 +126,64 @@ func TestSentPaymentSerialization(t *testing.T) { c, s := makeFakeInfo() var b bytes.Buffer - if err := serializePaymentCreationInfo(&b, c); err != nil { - t.Fatalf("unable to serialize creation info: %v", err) - } + require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") + + // Assert the length of the serialized creation info is as expected, + // without any custom records. + baseLength := 32 + 8 + 8 + 4 + len(c.PaymentRequest) + require.Len(t, b.Bytes(), baseLength) newCreationInfo, err := deserializePaymentCreationInfo(&b) - require.NoError(t, err, "unable to deserialize creation info") + require.NoError(t, err, "deserialize") + require.Equal(t, c, newCreationInfo) - if !reflect.DeepEqual(c, newCreationInfo) { - t.Fatalf("Payments do not match after "+ - "serialization/deserialization %v vs %v", - spew.Sdump(c), spew.Sdump(newCreationInfo), - ) + b.Reset() + + // Now we add some custom records to the creation info and serialize it + // again. + c.FirstHopCustomRecords = lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType: []byte{1, 2, 3}, } + require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize") + + newCreationInfo, err = deserializePaymentCreationInfo(&b) + require.NoError(t, err, "deserialize") + require.Equal(t, c, newCreationInfo) b.Reset() - if err := serializeHTLCAttemptInfo(&b, s); err != nil { - t.Fatalf("unable to serialize info: %v", err) - } + require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") newWireInfo, err := deserializeHTLCAttemptInfo(&b) - require.NoError(t, err, "unable to deserialize info") - newWireInfo.AttemptID = s.AttemptID + require.NoError(t, err, "deserialize") - // First we verify all the records match up porperly, as they aren't - // able to be properly compared using reflect.DeepEqual. - err = assertRouteEqual(&s.Route, &newWireInfo.Route) - if err != nil { - t.Fatalf("Routes do not match after "+ - "serialization/deserialization: %v", err) + // First we verify all the records match up properly. + require.Equal(t, s.Route, newWireInfo.Route) + + // We now add the new fields and custom records to the route and + // serialize it again. + b.Reset() + s.Route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( + tlv.NewBigSizeT(lnwire.MilliSatoshi(1234)), + ) + s.Route.FirstHopWireCustomRecords = lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType + 3: []byte{4, 5, 6}, } + require.NoError(t, serializeHTLCAttemptInfo(&b, s), "serialize") + + newWireInfo, err = deserializeHTLCAttemptInfo(&b) + require.NoError(t, err, "deserialize") + require.Equal(t, s.Route, newWireInfo.Route) // Clear routes to allow DeepEqual to compare the remaining fields. newWireInfo.Route = route.Route{} s.Route = route.Route{} + newWireInfo.AttemptID = s.AttemptID // Call session key method to set our cached session key so we can use // DeepEqual, and assert that our key equals the original key. require.Equal(t, s.cachedSessionKey, newWireInfo.SessionKey()) - if !reflect.DeepEqual(s, newWireInfo) { - t.Fatalf("Payments do not match after "+ - "serialization/deserialization %v vs %v", - spew.Sdump(s), spew.Sdump(newWireInfo), - ) - } + require.Equal(t, s, newWireInfo) } // assertRouteEquals compares to routes for equality and returns an error if diff --git a/channeldb/revocation_log.go b/channeldb/revocation_log.go index f062ac0860..3abc73f81e 100644 --- a/channeldb/revocation_log.go +++ b/channeldb/revocation_log.go @@ -7,6 +7,7 @@ import ( "math" "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -16,16 +17,15 @@ import ( const ( // OutputIndexEmpty is used when the output index doesn't exist. OutputIndexEmpty = math.MaxUint16 +) - // A set of tlv type definitions used to serialize the body of - // revocation logs to the database. - // - // NOTE: A migration should be added whenever this list changes. - revLogOurOutputIndexType tlv.Type = 0 - revLogTheirOutputIndexType tlv.Type = 1 - revLogCommitTxHashType tlv.Type = 2 - revLogOurBalanceType tlv.Type = 3 - revLogTheirBalanceType tlv.Type = 4 +type ( + // BigSizeAmount is a type alias for a TLV record of a btcutil.Amount. + BigSizeAmount = tlv.BigSizeT[btcutil.Amount] + + // BigSizeMilliSatoshi is a type alias for a TLV record of a + // lnwire.MilliSatoshi. + BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi] ) var ( @@ -54,6 +54,74 @@ var ( ErrOutputIndexTooBig = errors.New("output index is over uint16") ) +// SparsePayHash is a type alias for a 32 byte array, which when serialized is +// able to save some space by not including an empty payment hash on disk. +type SparsePayHash [32]byte + +// NewSparsePayHash creates a new SparsePayHash from a 32 byte array. +func NewSparsePayHash(rHash [32]byte) SparsePayHash { + return SparsePayHash(rHash) +} + +// Record returns a tlv record for the SparsePayHash. +func (s *SparsePayHash) Record() tlv.Record { + // We use a zero for the type here, as this'll be used along with the + // RecordT type. + return tlv.MakeDynamicRecord( + 0, s, s.hashLen, + sparseHashEncoder, sparseHashDecoder, + ) +} + +// hashLen is used by MakeDynamicRecord to return the size of the RHash. +// +// NOTE: for zero hash, we return a length 0. +func (s *SparsePayHash) hashLen() uint64 { + if bytes.Equal(s[:], lntypes.ZeroHash[:]) { + return 0 + } + + return 32 +} + +// sparseHashEncoder is the customized encoder which skips encoding the empty +// hash. +func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + v, ok := val.(*SparsePayHash) + if !ok { + return tlv.NewTypeForEncodingErr(val, "SparsePayHash") + } + + // If the value is an empty hash, we will skip encoding it. + if bytes.Equal(v[:], lntypes.ZeroHash[:]) { + return nil + } + + vArray := (*[32]byte)(v) + + return tlv.EBytes32(w, vArray, buf) +} + +// sparseHashDecoder is the customized decoder which skips decoding the empty +// hash. +func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + v, ok := val.(*SparsePayHash) + if !ok { + return tlv.NewTypeForEncodingErr(val, "SparsePayHash") + } + + // If the length is zero, we will skip encoding the empty hash. + if l == 0 { + return nil + } + + vArray := (*[32]byte)(v) + + return tlv.DBytes32(r, vArray, buf, 32) +} + // HTLCEntry specifies the minimal info needed to be stored on disk for ALL the // historical HTLCs, which is useful for constructing RevocationLog when a // breach is detected. @@ -72,116 +140,90 @@ var ( // made into tlv records without further conversion. type HTLCEntry struct { // RHash is the payment hash of the HTLC. - RHash [32]byte + RHash tlv.RecordT[tlv.TlvType0, SparsePayHash] // RefundTimeout is the absolute timeout on the HTLC that the sender // must wait before reclaiming the funds in limbo. - RefundTimeout uint32 + RefundTimeout tlv.RecordT[tlv.TlvType1, uint32] // OutputIndex is the output index for this particular HTLC output // within the commitment transaction. // // NOTE: we use uint16 instead of int32 here to save us 2 bytes, which // gives us a max number of HTLCs of 65K. - OutputIndex uint16 + OutputIndex tlv.RecordT[tlv.TlvType2, uint16] // Incoming denotes whether we're the receiver or the sender of this // HTLC. - // - // NOTE: this field is the memory representation of the field - // incomingUint. - Incoming bool + Incoming tlv.RecordT[tlv.TlvType3, bool] // Amt is the amount of satoshis this HTLC escrows. - // - // NOTE: this field is the memory representation of the field amtUint. - Amt btcutil.Amount + Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]] - // amtTlv is the uint64 format of Amt. This field is created so we can - // easily make it into a tlv record and save it to disk. - // - // NOTE: we keep this field for accounting purpose only. If the disk - // space becomes an issue, we could delete this field to save us extra - // 8 bytes. - amtTlv uint64 - - // incomingTlv is the uint8 format of Incoming. This field is created - // so we can easily make it into a tlv record and save it to disk. - incomingTlv uint8 -} + // CustomBlob is an optional blob that can be used to store information + // specific to revocation handling for a custom channel type. + CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] -// RHashLen is used by MakeDynamicRecord to return the size of the RHash. -// -// NOTE: for zero hash, we return a length 0. -func (h *HTLCEntry) RHashLen() uint64 { - if h.RHash == lntypes.ZeroHash { - return 0 - } - return 32 + // HtlcIndex is the index of the HTLC in the channel. + HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, uint16] } -// RHashEncoder is the customized encoder which skips encoding the empty hash. -func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error { - v, ok := val.(*[32]byte) - if !ok { - return tlv.NewTypeForEncodingErr(val, "RHash") - } - - // If the value is an empty hash, we will skip encoding it. - if *v == lntypes.ZeroHash { - return nil +// toTlvStream converts an HTLCEntry record into a tlv representation. +func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { + records := []tlv.Record{ + h.RHash.Record(), + h.RefundTimeout.Record(), + h.OutputIndex.Record(), + h.Incoming.Record(), + h.Amt.Record(), } - return tlv.EBytes32(w, v, buf) -} + h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { + records = append(records, r.Record()) + }) -// RHashDecoder is the customized decoder which skips decoding the empty hash. -func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error { - v, ok := val.(*[32]byte) - if !ok { - return tlv.NewTypeForEncodingErr(val, "RHash") - } + h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6, uint16]) { + records = append(records, r.Record()) + }) - // If the length is zero, we will skip encoding the empty hash. - if l == 0 { - return nil - } + tlv.SortRecords(records) - return tlv.DBytes32(r, v, buf, 32) + return tlv.NewStream(records...) } -// toTlvStream converts an HTLCEntry record into a tlv representation. -func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { - const ( - // A set of tlv type definitions used to serialize htlc entries - // to the database. We define it here instead of the head of - // the file to avoid naming conflicts. - // - // NOTE: A migration should be added whenever this list - // changes. - rHashType tlv.Type = 0 - refundTimeoutType tlv.Type = 1 - outputIndexType tlv.Type = 2 - incomingType tlv.Type = 3 - amtType tlv.Type = 4 - ) - - return tlv.NewStream( - tlv.MakeDynamicRecord( - rHashType, &h.RHash, h.RHashLen, - RHashEncoder, RHashDecoder, +// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC. +func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) { + h := &HTLCEntry{ + RHash: tlv.NewRecordT[tlv.TlvType0]( + NewSparsePayHash(htlc.RHash), ), - tlv.MakePrimitiveRecord( - refundTimeoutType, &h.RefundTimeout, + RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1]( + htlc.RefundTimeout, ), - tlv.MakePrimitiveRecord( - outputIndexType, &h.OutputIndex, + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2]( + uint16(htlc.OutputIndex), ), - tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv), - // We will save 3 bytes if the amount is less or equal to - // 4,294,967,295 msat, or roughly 0.043 bitcoin. - tlv.MakeBigSizeRecord(amtType, &h.amtTlv), - ) + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming), + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(htlc.Amt.ToSatoshis()), + ), + HtlcIndex: tlv.SomeRecordT(tlv.NewPrimitiveRecord[tlv.TlvType6]( + uint16(htlc.HtlcIndex), + )), + } + + if len(htlc.CustomRecords) != 0 { + blob, err := htlc.CustomRecords.Serialize() + if err != nil { + return nil, err + } + + h.CustomBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), + ) + } + + return h, nil } // RevocationLog stores the info needed to construct a breach retribution. Its @@ -191,15 +233,15 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { type RevocationLog struct { // OurOutputIndex specifies our output index in this commitment. In a // remote commitment transaction, this is the to remote output index. - OurOutputIndex uint16 + OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16] // TheirOutputIndex specifies their output index in this commitment. In // a remote commitment transaction, this is the to local output index. - TheirOutputIndex uint16 + TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16] // CommitTxHash is the hash of the latest version of the commitment // state, broadcast able by us. - CommitTxHash [32]byte + CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte] // HTLCEntries is the set of HTLCEntry's that are pending at this // particular commitment height. @@ -209,21 +251,65 @@ type RevocationLog struct { // directly spendable by us. In other words, it is the value of the // to_remote output on the remote parties' commitment transaction. // - // NOTE: this is a pointer so that it is clear if the value is zero or + // NOTE: this is an option so that it is clear if the value is zero or // nil. Since migration 30 of the channeldb initially did not include // this field, it could be the case that the field is not present for // all revocation logs. - OurBalance *lnwire.MilliSatoshi + OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi] // TheirBalance is the current available balance within the channel // directly spendable by the remote node. In other words, it is the // value of the to_local output on the remote parties' commitment. // - // NOTE: this is a pointer so that it is clear if the value is zero or + // NOTE: this is an option so that it is clear if the value is zero or // nil. Since migration 30 of the channeldb initially did not include // this field, it could be the case that the field is not present for // all revocation logs. - TheirBalance *lnwire.MilliSatoshi + TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi] + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This information is only created + // at channel funding time, and after wards is to be considered + // immutable. + CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob] +} + +// NewRevocationLog creates a new RevocationLog from the given parameters. +func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16, + commitHash [32]byte, ourBalance, + theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry, + customBlob fn.Option[tlv.Blob]) RevocationLog { + + rl := RevocationLog{ + OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( + ourOutputIndex, + ), + TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1]( + theirOutputIndex, + ), + CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash), + HTLCEntries: htlcs, + } + + ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) { + rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3]( + tlv.NewBigSizeT(balance), + )) + }) + + theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) { + rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(balance), + )) + }) + + customBlob.WhenSome(func(blob tlv.Blob) { + rl.CustomBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), + ) + }) + + return rl } // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a @@ -242,15 +328,32 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment, } rl := &RevocationLog{ - OurOutputIndex: uint16(ourOutputIndex), - TheirOutputIndex: uint16(theirOutputIndex), - CommitTxHash: commit.CommitTx.TxHash(), - HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), + OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( + uint16(ourOutputIndex), + ), + TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1]( + uint16(theirOutputIndex), + ), + CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte]( + commit.CommitTx.TxHash(), + ), + HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), } + commit.CustomBlob.WhenSome(func(blob tlv.Blob) { + rl.CustomBlob = tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob), + ) + }) + if !noAmtData { - rl.OurBalance = &commit.LocalBalance - rl.TheirBalance = &commit.RemoteBalance + rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3]( + tlv.NewBigSizeT(commit.LocalBalance), + )) + + rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(commit.RemoteBalance), + )) } for _, htlc := range commit.Htlcs { @@ -265,12 +368,9 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment, return ErrOutputIndexTooBig } - entry := &HTLCEntry{ - RHash: htlc.RHash, - RefundTimeout: htlc.RefundTimeout, - Incoming: htlc.Incoming, - OutputIndex: uint16(htlc.OutputIndex), - Amt: htlc.Amt.ToSatoshis(), + entry, err := NewHTLCEntryFromHTLC(htlc) + if err != nil { + return err } rl.HTLCEntries = append(rl.HTLCEntries, entry) } @@ -306,31 +406,27 @@ func fetchRevocationLog(log kvdb.RBucket, func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { // Add the tlv records for all non-optional fields. records := []tlv.Record{ - tlv.MakePrimitiveRecord( - revLogOurOutputIndexType, &rl.OurOutputIndex, - ), - tlv.MakePrimitiveRecord( - revLogTheirOutputIndexType, &rl.TheirOutputIndex, - ), - tlv.MakePrimitiveRecord( - revLogCommitTxHashType, &rl.CommitTxHash, - ), + rl.OurOutputIndex.Record(), + rl.TheirOutputIndex.Record(), + rl.CommitTxHash.Record(), } // Now we add any optional fields that are non-nil. - if rl.OurBalance != nil { - lb := uint64(*rl.OurBalance) - records = append(records, tlv.MakeBigSizeRecord( - revLogOurBalanceType, &lb, - )) - } + rl.OurBalance.WhenSome( + func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) { + records = append(records, r.Record()) + }, + ) - if rl.TheirBalance != nil { - rb := uint64(*rl.TheirBalance) - records = append(records, tlv.MakeBigSizeRecord( - revLogTheirBalanceType, &rb, - )) - } + rl.TheirBalance.WhenSome( + func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) { + records = append(records, r.Record()) + }, + ) + + rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) { + records = append(records, r.Record()) + }) // Create the tlv stream. tlvStream, err := tlv.NewStream(records...) @@ -351,14 +447,6 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { // format. func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { for _, htlc := range htlcs { - // Patch the incomingTlv field. - if htlc.Incoming { - htlc.incomingTlv = 1 - } - - // Patch the amtTlv field. - htlc.amtTlv = uint64(htlc.Amt) - // Create the tlv stream. tlvStream, err := htlc.toTlvStream() if err != nil { @@ -376,27 +464,20 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { // deserializeRevocationLog deserializes a RevocationLog based on tlv format. func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { - var ( - rl RevocationLog - ourBalance uint64 - theirBalance uint64 - ) + var rl RevocationLog + + ourBalance := rl.OurBalance.Zero() + theirBalance := rl.TheirBalance.Zero() + customBlob := rl.CustomBlob.Zero() // Create the tlv stream. tlvStream, err := tlv.NewStream( - tlv.MakePrimitiveRecord( - revLogOurOutputIndexType, &rl.OurOutputIndex, - ), - tlv.MakePrimitiveRecord( - revLogTheirOutputIndexType, &rl.TheirOutputIndex, - ), - tlv.MakePrimitiveRecord( - revLogCommitTxHashType, &rl.CommitTxHash, - ), - tlv.MakeBigSizeRecord(revLogOurBalanceType, &ourBalance), - tlv.MakeBigSizeRecord( - revLogTheirBalanceType, &theirBalance, - ), + rl.OurOutputIndex.Record(), + rl.TheirOutputIndex.Record(), + rl.CommitTxHash.Record(), + ourBalance.Record(), + theirBalance.Record(), + customBlob.Record(), ) if err != nil { return rl, err @@ -408,14 +489,16 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { return rl, err } - if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil { - lb := lnwire.MilliSatoshi(ourBalance) - rl.OurBalance = &lb + if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil { + rl.OurBalance = tlv.SomeRecordT(ourBalance) + } + + if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil { + rl.TheirBalance = tlv.SomeRecordT(theirBalance) } - if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil { - rb := lnwire.MilliSatoshi(theirBalance) - rl.TheirBalance = &rb + if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { + rl.CustomBlob = tlv.SomeRecordT(customBlob) } // Read the HTLC entries. @@ -432,14 +515,28 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { for { var htlc HTLCEntry + customBlob := htlc.CustomBlob.Zero() + htlcIndex := htlc.HtlcIndex.Zero() + // Create the tlv stream. - tlvStream, err := htlc.toTlvStream() + records := []tlv.Record{ + htlc.RHash.Record(), + htlc.RefundTimeout.Record(), + htlc.OutputIndex.Record(), + htlc.Incoming.Record(), + htlc.Amt.Record(), + customBlob.Record(), + htlcIndex.Record(), + } + + tlvStream, err := tlv.NewStream(records...) if err != nil { return nil, err } // Read the HTLC entry. - if _, err := readTlvStream(r, tlvStream); err != nil { + parsedTypes, err := readTlvStream(r, tlvStream) + if err != nil { // We've reached the end when hitting an EOF. if err == io.ErrUnexpectedEOF { break @@ -447,13 +544,13 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) { return nil, err } - // Patch the Incoming field. - if htlc.incomingTlv == 1 { - htlc.Incoming = true + if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil { + htlc.CustomBlob = tlv.SomeRecordT(customBlob) } - // Patch the Amt field. - htlc.Amt = btcutil.Amount(htlc.amtTlv) + if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil { + htlc.HtlcIndex = tlv.SomeRecordT(htlcIndex) + } // Append the entry. htlcs = append(htlcs, &htlc) @@ -469,6 +566,7 @@ func writeTlvStream(w io.Writer, s *tlv.Stream) error { if err := s.Encode(&b); err != nil { return err } + // Write the stream's length as a varint. err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{}) if err != nil { diff --git a/channeldb/revocation_log_test.go b/channeldb/revocation_log_test.go index fc5303a48d..4290552eee 100644 --- a/channeldb/revocation_log_test.go +++ b/channeldb/revocation_log_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntest/channels" "github.com/lightningnetwork/lnd/lnwire" @@ -33,17 +34,38 @@ var ( 0xff, // value = 255 } + customRecords = lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType + 1: []byte("custom data"), + } + + blobBytes = []byte{ + // Corresponds to the encoded version of the above custom + // records. + 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, + } + testHTLCEntry = HTLCEntry{ - RefundTimeout: 740_000, - OutputIndex: 10, - Incoming: true, - Amt: 1000_000, - amtTlv: 1000_000, - incomingTlv: 1, + RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32]( + 740_000, + ), + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16]( + 10, + ), + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true), + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(btcutil.Amount(1_000_000)), + ), + CustomBlob: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType5](blobBytes), + ), + HtlcIndex: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[tlv.TlvType6, uint16](0x33), + ), } testHTLCEntryBytes = []byte{ - // Body length 23. - 0x16, + // Body length 45. + 0x2d, // Rhash tlv. 0x0, 0x0, // RefundTimeout tlv. @@ -54,6 +76,45 @@ var ( 0x3, 0x1, 0x1, // Amt tlv. 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, + // Custom blob tlv. + 0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, + // HLTC index tlv. + 0x6, 0x2, 0x0, 0x33, + } + + testHTLCEntryHash = HTLCEntry{ + RHash: tlv.NewPrimitiveRecord[tlv.TlvType0](NewSparsePayHash( + [32]byte{0x33, 0x44, 0x55}, + )), + RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32]( + 740_000, + ), + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16]( + 10, + ), + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true), + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(btcutil.Amount(1_000_000)), + ), + } + testHTLCEntryHashBytes = []byte{ + // Body length 54. + 0x36, + // Rhash tlv. + 0x0, 0x20, + 0x33, 0x44, 0x55, 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, + // RefundTimeout tlv. + 0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0, + // OutputIndex tlv. + 0x2, 0x2, 0x0, 0xa, + // Incoming tlv. + 0x3, 0x1, 0x1, + // Amt tlv. + 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, } localBalance = lnwire.MilliSatoshi(9000) @@ -68,24 +129,29 @@ var ( CommitTx: channels.TestFundingTx, CommitSig: bytes.Repeat([]byte{1}, 71), Htlcs: []HTLC{{ - RefundTimeout: testHTLCEntry.RefundTimeout, - OutputIndex: int32(testHTLCEntry.OutputIndex), - Incoming: testHTLCEntry.Incoming, + RefundTimeout: testHTLCEntry.RefundTimeout.Val, + OutputIndex: int32(testHTLCEntry.OutputIndex.Val), + HtlcIndex: uint64( + testHTLCEntry.HtlcIndex.ValOpt(). + UnsafeFromSome(), + ), + Incoming: testHTLCEntry.Incoming.Val, Amt: lnwire.NewMSatFromSatoshis( - testHTLCEntry.Amt, + testHTLCEntry.Amt.Val.Int(), ), + CustomRecords: customRecords, }}, + CustomBlob: fn.Some(blobBytes), } - testRevocationLogNoAmts = RevocationLog{ - OurOutputIndex: 0, - TheirOutputIndex: 1, - CommitTxHash: testChannelCommit.CommitTx.TxHash(), - HTLCEntries: []*HTLCEntry{&testHTLCEntry}, - } + testRevocationLogNoAmts = NewRevocationLog( + 0, 1, testChannelCommit.CommitTx.TxHash(), + fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](), + []*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes), + ) testRevocationLogNoAmtsBytes = []byte{ - // Body length 42. - 0x2a, + // Body length 61. + 0x3d, // OurOutputIndex tlv. 0x0, 0x2, 0x0, 0x0, // TheirOutputIndex tlv. @@ -96,19 +162,19 @@ var ( 0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6, 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, 0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, + // Custom blob tlv. + 0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, } - testRevocationLogWithAmts = RevocationLog{ - OurOutputIndex: 0, - TheirOutputIndex: 1, - CommitTxHash: testChannelCommit.CommitTx.TxHash(), - HTLCEntries: []*HTLCEntry{&testHTLCEntry}, - OurBalance: &localBalance, - TheirBalance: &remoteBalance, - } + testRevocationLogWithAmts = NewRevocationLog( + 0, 1, testChannelCommit.CommitTx.TxHash(), + fn.Some(localBalance), fn.Some(remoteBalance), + []*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes), + ) testRevocationLogWithAmtsBytes = []byte{ - // Body length 52. - 0x34, + // Body length 71. + 0x47, // OurOutputIndex tlv. 0x0, 0x2, 0x0, 0x0, // TheirOutputIndex tlv. @@ -123,6 +189,9 @@ var ( 0x3, 0x3, 0xfd, 0x23, 0x28, // Remote Balance. 0x4, 0x3, 0xfd, 0x0b, 0xb8, + // Custom blob tlv. + 0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, } ) @@ -193,11 +262,6 @@ func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) { // Copy the testHTLCEntry. entry := testHTLCEntry - // Set the internal fields to empty values so we can test the bytes are - // padded. - entry.incomingTlv = 0 - entry.amtTlv = 0 - // Write the tlv stream. buf := bytes.NewBuffer([]byte{}) err := serializeHTLCEntries(buf, []*HTLCEntry{&entry}) @@ -207,6 +271,21 @@ func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) { require.Equal(t, testHTLCEntryBytes, buf.Bytes()) } +func TestSerializeHTLCEntriesWithRHash(t *testing.T) { + t.Parallel() + + // Copy the testHTLCEntry. + entry := testHTLCEntryHash + + // Write the tlv stream. + buf := bytes.NewBuffer([]byte{}) + err := serializeHTLCEntries(buf, []*HTLCEntry{&entry}) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Equal(t, testHTLCEntryHashBytes, buf.Bytes()) +} + func TestSerializeHTLCEntries(t *testing.T) { t.Parallel() @@ -215,7 +294,7 @@ func TestSerializeHTLCEntries(t *testing.T) { // Create a fake rHash. rHashBytes := bytes.Repeat([]byte{10}, 32) - copy(entry.RHash[:], rHashBytes) + copy(entry.RHash.Val[:], rHashBytes) // Construct the serialized bytes. // @@ -224,7 +303,7 @@ func TestSerializeHTLCEntries(t *testing.T) { partialBytes := testHTLCEntryBytes[3:] // Write the total length and RHash tlv. - expectedBytes := []byte{0x36, 0x0, 0x20} + expectedBytes := []byte{0x4d, 0x0, 0x20} expectedBytes = append(expectedBytes, rHashBytes...) // Append the rest. @@ -269,7 +348,7 @@ func TestSerializeAndDeserializeRevLog(t *testing.T) { t, &test.revLog, test.revLogBytes, ) - testDerializeRevocationLog( + testDeserializeRevocationLog( t, &test.revLog, test.revLogBytes, ) }) @@ -293,7 +372,7 @@ func testSerializeRevocationLog(t *testing.T, rl *RevocationLog, require.Equal(t, revLogBytes, buf.Bytes()[:bodyIndex]) } -func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog, +func testDeserializeRevocationLog(t *testing.T, revLog *RevocationLog, revLogBytes []byte) { // Construct the full bytes. @@ -309,7 +388,7 @@ func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog, require.Equal(t, *revLog, rl) } -func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) { +func TestDeserializeHTLCEntriesEmptyRHash(t *testing.T) { t.Parallel() // Read the tlv stream. @@ -322,7 +401,7 @@ func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) { require.Equal(t, &testHTLCEntry, htlcs[0]) } -func TestDerializeHTLCEntries(t *testing.T) { +func TestDeserializeHTLCEntries(t *testing.T) { t.Parallel() // Copy the testHTLCEntry. @@ -330,7 +409,7 @@ func TestDerializeHTLCEntries(t *testing.T) { // Create a fake rHash. rHashBytes := bytes.Repeat([]byte{10}, 32) - copy(entry.RHash[:], rHashBytes) + copy(entry.RHash.Val[:], rHashBytes) // Construct the serialized bytes. // @@ -339,7 +418,7 @@ func TestDerializeHTLCEntries(t *testing.T) { partialBytes := testHTLCEntryBytes[3:] // Write the total length and RHash tlv. - testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...) + testBytes := append([]byte{0x4d, 0x0, 0x20}, rHashBytes...) // Append the rest. testBytes = append(testBytes, partialBytes...) @@ -398,11 +477,11 @@ func TestDeleteLogBucket(t *testing.T) { err = kvdb.Update(backend, func(tx kvdb.RwTx) error { // Create the buckets. - chanBucket, _, err := createTestRevocatoinLogBuckets(tx) + chanBucket, _, err := createTestRevocationLogBuckets(tx) require.NoError(t, err) // Create the buckets again should give us an error. - _, _, err = createTestRevocatoinLogBuckets(tx) + _, _, err = createTestRevocationLogBuckets(tx) require.ErrorIs(t, err, kvdb.ErrBucketExists) // Delete both buckets. @@ -410,7 +489,7 @@ func TestDeleteLogBucket(t *testing.T) { require.NoError(t, err) // Create the buckets again should give us NO error. - _, _, err = createTestRevocatoinLogBuckets(tx) + _, _, err = createTestRevocationLogBuckets(tx) return err }, func() {}) require.NoError(t, err) @@ -516,7 +595,7 @@ func TestPutRevocationLog(t *testing.T) { // Construct the testing db transaction. dbTx := func(tx kvdb.RwTx) (RevocationLog, error) { // Create the buckets. - _, bucket, err := createTestRevocatoinLogBuckets(tx) + _, bucket, err := createTestRevocationLogBuckets(tx) require.NoError(t, err) // Save the log. @@ -686,7 +765,7 @@ func TestFetchRevocationLogCompatible(t *testing.T) { } } -func createTestRevocatoinLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket, +func createTestRevocationLogBuckets(tx kvdb.RwTx) (kvdb.RwBucket, kvdb.RwBucket, error) { chanBucket, err := tx.CreateTopLevelBucket(openChannelBucket) diff --git a/cmd/lncli/arg_parse.go b/cmd/commands/arg_parse.go similarity index 90% rename from cmd/lncli/arg_parse.go rename to cmd/commands/arg_parse.go index 49d165d556..1d8bbe1988 100644 --- a/cmd/lncli/arg_parse.go +++ b/cmd/commands/arg_parse.go @@ -1,4 +1,4 @@ -package main +package commands import ( "regexp" @@ -42,7 +42,7 @@ func parseTime(s string, base time.Time) (uint64, error) { var lightningPrefix = "lightning:" -// stripPrefix removes accidentally copied 'lightning:' prefix. -func stripPrefix(s string) string { +// StripPrefix removes accidentally copied 'lightning:' prefix. +func StripPrefix(s string) string { return strings.TrimSpace(strings.TrimPrefix(s, lightningPrefix)) } diff --git a/cmd/lncli/arg_parse_test.go b/cmd/commands/arg_parse_test.go similarity index 97% rename from cmd/lncli/arg_parse_test.go rename to cmd/commands/arg_parse_test.go index 571292d2c6..35751098e4 100644 --- a/cmd/lncli/arg_parse_test.go +++ b/cmd/commands/arg_parse_test.go @@ -1,4 +1,4 @@ -package main +package commands import ( "testing" @@ -111,7 +111,7 @@ func TestStripPrefix(t *testing.T) { t.Parallel() for _, test := range stripPrefixTests { - actual := stripPrefix(test.in) + actual := StripPrefix(test.in) require.Equal(t, test.expected, actual) } } diff --git a/cmd/lncli/autopilotrpc_active.go b/cmd/commands/autopilotrpc_active.go similarity index 99% rename from cmd/lncli/autopilotrpc_active.go rename to cmd/commands/autopilotrpc_active.go index 961e859947..212ef45797 100644 --- a/cmd/lncli/autopilotrpc_active.go +++ b/cmd/commands/autopilotrpc_active.go @@ -1,7 +1,7 @@ //go:build autopilotrpc // +build autopilotrpc -package main +package commands import ( "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc" diff --git a/cmd/lncli/autopilotrpc_default.go b/cmd/commands/autopilotrpc_default.go similarity index 92% rename from cmd/lncli/autopilotrpc_default.go rename to cmd/commands/autopilotrpc_default.go index 7fb8852170..393b6f124f 100644 --- a/cmd/lncli/autopilotrpc_default.go +++ b/cmd/commands/autopilotrpc_default.go @@ -1,7 +1,7 @@ //go:build !autopilotrpc // +build !autopilotrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/chainrpc_active.go b/cmd/commands/chainrpc_active.go similarity index 99% rename from cmd/lncli/chainrpc_active.go rename to cmd/commands/chainrpc_active.go index 48946e0d5d..0f1f8b6121 100644 --- a/cmd/lncli/chainrpc_active.go +++ b/cmd/commands/chainrpc_active.go @@ -1,7 +1,7 @@ //go:build chainrpc // +build chainrpc -package main +package commands import ( "bytes" diff --git a/cmd/lncli/chainrpc_default.go b/cmd/commands/chainrpc_default.go similarity index 91% rename from cmd/lncli/chainrpc_default.go rename to cmd/commands/chainrpc_default.go index fa1ea99e2c..28440a839e 100644 --- a/cmd/lncli/chainrpc_default.go +++ b/cmd/commands/chainrpc_default.go @@ -1,7 +1,7 @@ //go:build !chainrpc // +build !chainrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/cmd_custom.go b/cmd/commands/cmd_custom.go similarity index 98% rename from cmd/lncli/cmd_custom.go rename to cmd/commands/cmd_custom.go index 7ff5d8a71e..728d70bd39 100644 --- a/cmd/lncli/cmd_custom.go +++ b/cmd/commands/cmd_custom.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" diff --git a/cmd/lncli/cmd_debug.go b/cmd/commands/cmd_debug.go similarity index 99% rename from cmd/lncli/cmd_debug.go rename to cmd/commands/cmd_debug.go index 758bff576d..37024f5ecf 100644 --- a/cmd/lncli/cmd_debug.go +++ b/cmd/commands/cmd_debug.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" diff --git a/cmd/lncli/cmd_import_mission_control.go b/cmd/commands/cmd_import_mission_control.go similarity index 99% rename from cmd/lncli/cmd_import_mission_control.go rename to cmd/commands/cmd_import_mission_control.go index 420396322f..ac15b2f1f4 100644 --- a/cmd/lncli/cmd_import_mission_control.go +++ b/cmd/commands/cmd_import_mission_control.go @@ -1,4 +1,4 @@ -package main +package commands import ( "context" diff --git a/cmd/lncli/cmd_invoice.go b/cmd/commands/cmd_invoice.go similarity index 99% rename from cmd/lncli/cmd_invoice.go rename to cmd/commands/cmd_invoice.go index a7ed2b8b2e..5ea4c1e0b0 100644 --- a/cmd/lncli/cmd_invoice.go +++ b/cmd/commands/cmd_invoice.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" @@ -9,7 +9,7 @@ import ( "github.com/urfave/cli" ) -var addInvoiceCommand = cli.Command{ +var AddInvoiceCommand = cli.Command{ Name: "addinvoice", Category: "Invoices", Usage: "Add a new invoice.", @@ -408,7 +408,7 @@ func decodePayReq(ctx *cli.Context) error { } resp, err := client.DecodePayReq(ctxc, &lnrpc.PayReqString{ - PayReq: stripPrefix(payreq), + PayReq: StripPrefix(payreq), }) if err != nil { return err diff --git a/cmd/lncli/cmd_macaroon.go b/cmd/commands/cmd_macaroon.go similarity index 99% rename from cmd/lncli/cmd_macaroon.go rename to cmd/commands/cmd_macaroon.go index deea4e7ad3..149c7db453 100644 --- a/cmd/lncli/cmd_macaroon.go +++ b/cmd/commands/cmd_macaroon.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" diff --git a/cmd/lncli/cmd_mission_control.go b/cmd/commands/cmd_mission_control.go similarity index 99% rename from cmd/lncli/cmd_mission_control.go rename to cmd/commands/cmd_mission_control.go index 323acdff6d..fe4acb25cf 100644 --- a/cmd/lncli/cmd_mission_control.go +++ b/cmd/commands/cmd_mission_control.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" @@ -265,6 +265,7 @@ func setCfg(ctx *cli.Context) error { Config: mcCfg.Config, }, ) + return err } @@ -366,5 +367,6 @@ func resetMissionControl(ctx *cli.Context) error { req := &routerrpc.ResetMissionControlRequest{} _, err := client.ResetMissionControl(ctxc, req) + return err } diff --git a/cmd/lncli/cmd_open_channel.go b/cmd/commands/cmd_open_channel.go similarity index 99% rename from cmd/lncli/cmd_open_channel.go rename to cmd/commands/cmd_open_channel.go index f0585ed47a..b4fe83f20b 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/commands/cmd_open_channel.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" diff --git a/cmd/lncli/cmd_payments.go b/cmd/commands/cmd_payments.go similarity index 95% rename from cmd/lncli/cmd_payments.go rename to cmd/commands/cmd_payments.go index 228dd3415f..c8787a6cc1 100644 --- a/cmd/lncli/cmd_payments.go +++ b/cmd/commands/cmd_payments.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" @@ -25,6 +25,7 @@ import ( "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" "github.com/urfave/cli" + "google.golang.org/grpc" ) const ( @@ -152,8 +153,8 @@ var ( } ) -// paymentFlags returns common flags for sendpayment and payinvoice. -func paymentFlags() []cli.Flag { +// PaymentFlags returns common flags for sendpayment and payinvoice. +func PaymentFlags() []cli.Flag { return []cli.Flag{ cli.StringFlag{ Name: "pay_req", @@ -202,7 +203,7 @@ func paymentFlags() []cli.Flag { } } -var sendPaymentCommand = cli.Command{ +var SendPaymentCommand = cli.Command{ Name: "sendpayment", Category: "Payments", Usage: "Send a payment over lightning.", @@ -226,7 +227,7 @@ var sendPaymentCommand = cli.Command{ `, ArgsUsage: "dest amt payment_hash final_cltv_delta pay_addr | " + "--pay_req=R [--pay_addr=H]", - Flags: append(paymentFlags(), + Flags: append(PaymentFlags(), cli.StringFlag{ Name: "dest, d", Usage: "the compressed identity pubkey of the " + @@ -253,7 +254,7 @@ var sendPaymentCommand = cli.Command{ Usage: "will generate a pre-image and encode it in the sphinx packet, a dest must be set [experimental]", }, ), - Action: sendPayment, + Action: SendPayment, } // retrieveFeeLimit retrieves the fee limit based on the different fee limit @@ -324,20 +325,23 @@ func parsePayAddr(ctx *cli.Context, args cli.Args) ([]byte, error) { return payAddr, nil } -func sendPayment(ctx *cli.Context) error { +func SendPayment(ctx *cli.Context) error { // Show command help if no arguments provided if ctx.NArg() == 0 && ctx.NumFlags() == 0 { _ = cli.ShowCommandHelp(ctx, "sendpayment") return nil } + conn := getClientConn(ctx, false) + defer conn.Close() + args := ctx.Args() // If a payment request was provided, we can exit early since all of the // details of the payment are encoded within the request. if ctx.IsSet("pay_req") { req := &routerrpc.SendPaymentRequest{ - PaymentRequest: stripPrefix(ctx.String("pay_req")), + PaymentRequest: StripPrefix(ctx.String("pay_req")), Amt: ctx.Int64("amt"), DestCustomRecords: make(map[uint64][]byte), Amp: ctx.Bool(ampFlag.Name), @@ -357,7 +361,9 @@ func sendPayment(ctx *cli.Context) error { req.PaymentAddr = payAddr - return sendPaymentRequest(ctx, req) + return SendPaymentRequest( + ctx, req, conn, conn, routerRPCSendPayment, + ) } var ( @@ -466,19 +472,29 @@ func sendPayment(ctx *cli.Context) error { req.PaymentAddr = payAddr - return sendPaymentRequest(ctx, req) + return SendPaymentRequest(ctx, req, conn, conn, routerRPCSendPayment) } -func sendPaymentRequest(ctx *cli.Context, - req *routerrpc.SendPaymentRequest) error { +// SendPaymentFn is a function type that abstracts the SendPaymentV2 call of the +// router client. +type SendPaymentFn func(ctx context.Context, payConn grpc.ClientConnInterface, + req *routerrpc.SendPaymentRequest) (PaymentResultStream, error) - ctxc := getContext() +// routerRPCSendPayment is the default implementation of the SendPaymentFn type +// that uses the lnd routerrpc.SendPaymentV2 call. +func routerRPCSendPayment(ctx context.Context, payConn grpc.ClientConnInterface, + req *routerrpc.SendPaymentRequest) (PaymentResultStream, error) { - conn := getClientConn(ctx, false) - defer conn.Close() + return routerrpc.NewRouterClient(payConn).SendPaymentV2(ctx, req) +} - client := lnrpc.NewLightningClient(conn) - routerClient := routerrpc.NewRouterClient(conn) +func SendPaymentRequest(ctx *cli.Context, req *routerrpc.SendPaymentRequest, + lnConn, paymentConn grpc.ClientConnInterface, + callSendPayment SendPaymentFn) error { + + ctxc := getContext() + + lnClient := lnrpc.NewLightningClient(lnConn) outChan := ctx.Int64Slice("outgoing_chan_id") if len(outChan) != 0 { @@ -558,7 +574,7 @@ func sendPaymentRequest(ctx *cli.Context, if req.PaymentRequest != "" { // Decode payment request to find out the amount. decodeReq := &lnrpc.PayReqString{PayReq: req.PaymentRequest} - decodeResp, err := client.DecodePayReq(ctxc, decodeReq) + decodeResp, err := lnClient.DecodePayReq(ctxc, decodeReq) if err != nil { return err } @@ -602,14 +618,12 @@ func sendPaymentRequest(ctx *cli.Context, printJSON := ctx.Bool(jsonFlag.Name) req.NoInflightUpdates = !ctx.Bool(inflightUpdatesFlag.Name) && printJSON - stream, err := routerClient.SendPaymentV2(ctxc, req) + stream, err := callSendPayment(ctxc, paymentConn, req) if err != nil { return err } - finalState, err := printLivePayment( - ctxc, stream, client, printJSON, - ) + finalState, err := PrintLivePayment(ctxc, stream, lnClient, printJSON) if err != nil { return err } @@ -667,24 +681,29 @@ func trackPayment(ctx *cli.Context) error { } client := lnrpc.NewLightningClient(conn) - _, err = printLivePayment(ctxc, stream, client, ctx.Bool(jsonFlag.Name)) + _, err = PrintLivePayment(ctxc, stream, client, ctx.Bool(jsonFlag.Name)) return err } -// printLivePayment receives payment updates from the given stream and either +// PaymentResultStream is an interface that abstracts the Recv method of the +// SendPaymentV2 or TrackPaymentV2 client stream. +type PaymentResultStream interface { + Recv() (*lnrpc.Payment, error) +} + +// PrintLivePayment receives payment updates from the given stream and either // outputs them as json or as a more user-friendly formatted table. The table // option uses terminal control codes to rewrite the output. This call // terminates when the payment reaches a final state. -func printLivePayment(ctxc context.Context, - stream routerrpc.Router_TrackPaymentV2Client, - client lnrpc.LightningClient, json bool) (*lnrpc.Payment, error) { +func PrintLivePayment(ctxc context.Context, stream PaymentResultStream, + lnClient lnrpc.LightningClient, json bool) (*lnrpc.Payment, error) { // Terminal escape codes aren't supported on Windows, fall back to json. if !json && runtime.GOOS == "windows" { json = true } - aliases := newAliasCache(client) + aliases := newAliasCache(lnClient) first := true var lastLineCount int @@ -706,17 +725,17 @@ func printLivePayment(ctxc context.Context, // Write raw json to stdout. printRespJSON(payment) } else { - table := formatPayment(ctxc, payment, aliases) + resultTable := formatPayment(ctxc, payment, aliases) // Clear all previously written lines and print the // updated table. clearLines(lastLineCount) - fmt.Print(table) + fmt.Print(resultTable) // Store the number of lines written for the next update // pass. lastLineCount = 0 - for _, b := range table { + for _, b := range resultTable { if b == '\n' { lastLineCount++ } @@ -874,7 +893,7 @@ var payInvoiceCommand = cli.Command{ This command is a shortcut for 'sendpayment --pay_req='. `, ArgsUsage: "pay_req", - Flags: append(paymentFlags(), + Flags: append(PaymentFlags(), cli.Int64Flag{ Name: "amt", Usage: "(optional) number of satoshis to fulfill the " + @@ -885,6 +904,9 @@ var payInvoiceCommand = cli.Command{ } func payInvoice(ctx *cli.Context) error { + conn := getClientConn(ctx, false) + defer conn.Close() + args := ctx.Args() var payReq string @@ -898,14 +920,14 @@ func payInvoice(ctx *cli.Context) error { } req := &routerrpc.SendPaymentRequest{ - PaymentRequest: stripPrefix(payReq), + PaymentRequest: StripPrefix(payReq), Amt: ctx.Int64("amt"), DestCustomRecords: make(map[uint64][]byte), Amp: ctx.Bool(ampFlag.Name), Cancelable: ctx.Bool(cancelableFlag.Name), } - return sendPaymentRequest(ctx, req) + return SendPaymentRequest(ctx, req, conn, conn, routerRPCSendPayment) } var sendToRouteCommand = cli.Command{ @@ -1900,7 +1922,7 @@ func estimateRouteFee(ctx *cli.Context) error { req.AmtSat = amtSat case ctx.IsSet("pay_req"): - req.PaymentRequest = stripPrefix(ctx.String("pay_req")) + req.PaymentRequest = StripPrefix(ctx.String("pay_req")) if ctx.IsSet("timeout") { req.Timeout = uint32(ctx.Duration("timeout").Seconds()) } diff --git a/cmd/lncli/cmd_profile.go b/cmd/commands/cmd_profile.go similarity index 99% rename from cmd/lncli/cmd_profile.go rename to cmd/commands/cmd_profile.go index 3b1bf64875..6767964eb4 100644 --- a/cmd/lncli/cmd_profile.go +++ b/cmd/commands/cmd_profile.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/lncli/cmd_state.go b/cmd/commands/cmd_state.go similarity index 98% rename from cmd/lncli/cmd_state.go rename to cmd/commands/cmd_state.go index afca13e9d6..c2522b721b 100644 --- a/cmd/lncli/cmd_state.go +++ b/cmd/commands/cmd_state.go @@ -1,4 +1,4 @@ -package main +package commands import ( "context" diff --git a/cmd/lncli/cmd_update_chan_status.go b/cmd/commands/cmd_update_chan_status.go similarity index 99% rename from cmd/lncli/cmd_update_chan_status.go rename to cmd/commands/cmd_update_chan_status.go index 23c22f0b16..3525f7c5c6 100644 --- a/cmd/lncli/cmd_update_chan_status.go +++ b/cmd/commands/cmd_update_chan_status.go @@ -1,4 +1,4 @@ -package main +package commands import ( "errors" diff --git a/cmd/lncli/cmd_version.go b/cmd/commands/cmd_version.go similarity index 98% rename from cmd/lncli/cmd_version.go rename to cmd/commands/cmd_version.go index 99cc729953..9e7a2b0775 100644 --- a/cmd/lncli/cmd_version.go +++ b/cmd/commands/cmd_version.go @@ -1,4 +1,4 @@ -package main +package commands import ( "fmt" diff --git a/cmd/lncli/cmd_walletunlocker.go b/cmd/commands/cmd_walletunlocker.go similarity index 99% rename from cmd/lncli/cmd_walletunlocker.go rename to cmd/commands/cmd_walletunlocker.go index 650ef80ea5..844b48f3d6 100644 --- a/cmd/lncli/cmd_walletunlocker.go +++ b/cmd/commands/cmd_walletunlocker.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bufio" diff --git a/cmd/lncli/commands.go b/cmd/commands/commands.go similarity index 97% rename from cmd/lncli/commands.go rename to cmd/commands/commands.go index fefef6ab28..06759990f1 100644 --- a/cmd/lncli/commands.go +++ b/cmd/commands/commands.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bufio" @@ -11,6 +11,7 @@ import ( "io" "math" "os" + "regexp" "strconv" "strings" "sync" @@ -41,8 +42,49 @@ const ( defaultUtxoMinConf = 1 ) -var errBadChanPoint = errors.New("expecting chan_point to be in format of: " + - "txid:index") +var ( + errBadChanPoint = errors.New( + "expecting chan_point to be in format of: txid:index", + ) + + customDataPattern = regexp.MustCompile( + `"custom_channel_data":\s*"([0-9a-f]+)"`, + ) +) + +// replaceCustomData replaces the custom channel data hex string with the +// decoded custom channel data in the JSON response. +func replaceCustomData(jsonBytes []byte) []byte { + // If there's nothing to replace, return the original JSON. + if !customDataPattern.Match(jsonBytes) { + return jsonBytes + } + + replacedBytes := customDataPattern.ReplaceAllFunc( + jsonBytes, func(match []byte) []byte { + encoded := customDataPattern.FindStringSubmatch( + string(match), + )[1] + decoded, err := hex.DecodeString(encoded) + if err != nil { + return match + } + + return []byte("\"custom_channel_data\":" + + string(decoded)) + }, + ) + + var buf bytes.Buffer + err := json.Indent(&buf, replacedBytes, "", " ") + if err != nil { + // If we can't indent the JSON, it likely means the replacement + // data wasn't correct, so we return the original JSON. + return jsonBytes + } + + return buf.Bytes() +} func getContext() context.Context { shutdownInterceptor, err := signal.Intercept() @@ -66,9 +108,9 @@ func printJSON(resp interface{}) { } var out bytes.Buffer - json.Indent(&out, b, "", "\t") - out.WriteString("\n") - out.WriteTo(os.Stdout) + _ = json.Indent(&out, b, "", " ") + _, _ = out.WriteString("\n") + _, _ = out.WriteTo(os.Stdout) } func printRespJSON(resp proto.Message) { @@ -78,7 +120,9 @@ func printRespJSON(resp proto.Message) { return } - fmt.Printf("%s\n", jsonBytes) + jsonBytesReplaced := replaceCustomData(jsonBytes) + + fmt.Printf("%s\n", jsonBytesReplaced) } // actionDecorator is used to add additional information and error handling @@ -1442,15 +1486,15 @@ func walletBalance(ctx *cli.Context) error { return nil } -var channelBalanceCommand = cli.Command{ +var ChannelBalanceCommand = cli.Command{ Name: "channelbalance", Category: "Channels", Usage: "Returns the sum of the total available channel balance across " + "all open channels.", - Action: actionDecorator(channelBalance), + Action: actionDecorator(ChannelBalance), } -func channelBalance(ctx *cli.Context) error { +func ChannelBalance(ctx *cli.Context) error { ctxc := getContext() client, cleanUp := getClient(ctx) defer cleanUp() @@ -1575,7 +1619,7 @@ func pendingChannels(ctx *cli.Context) error { return nil } -var listChannelsCommand = cli.Command{ +var ListChannelsCommand = cli.Command{ Name: "listchannels", Category: "Channels", Usage: "List all open channels.", @@ -1608,7 +1652,7 @@ var listChannelsCommand = cli.Command{ "order to improve performance", }, }, - Action: actionDecorator(listChannels), + Action: actionDecorator(ListChannels), } var listAliasesCommand = cli.Command{ @@ -1616,10 +1660,10 @@ var listAliasesCommand = cli.Command{ Category: "Channels", Usage: "List all aliases.", Flags: []cli.Flag{}, - Action: actionDecorator(listaliases), + Action: actionDecorator(listAliases), } -func listaliases(ctx *cli.Context) error { +func listAliases(ctx *cli.Context) error { ctxc := getContext() client, cleanUp := getClient(ctx) defer cleanUp() @@ -1636,7 +1680,7 @@ func listaliases(ctx *cli.Context) error { return nil } -func listChannels(ctx *cli.Context) error { +func ListChannels(ctx *cli.Context) error { ctxc := getContext() client, cleanUp := getClient(ctx) defer cleanUp() diff --git a/cmd/lncli/commands_test.go b/cmd/commands/commands_test.go similarity index 60% rename from cmd/lncli/commands_test.go rename to cmd/commands/commands_test.go index a1f967561e..cb9cbe0db8 100644 --- a/cmd/lncli/commands_test.go +++ b/cmd/commands/commands_test.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" @@ -120,3 +120,74 @@ func TestParseTimeLockDelta(t *testing.T) { } } } + +// TestReplaceCustomData tests that hex encoded custom data can be formatted as +// JSON in the console output. +func TestReplaceCustomData(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + data string + replaceData string + expected string + }{ + { + name: "no replacement necessary", + data: "foo", + expected: "foo", + }, + { + name: "valid json with replacement", + data: "{\"foo\":\"bar\",\"custom_channel_data\":\"" + + hex.EncodeToString([]byte( + "{\"bar\":\"baz\"}", + )) + "\"}", + expected: `{ + "foo": "bar", + "custom_channel_data": { + "bar": "baz" + } +}`, + }, + { + name: "valid json with replacement and space", + data: "{\"foo\":\"bar\",\"custom_channel_data\": \"" + + hex.EncodeToString([]byte( + "{\"bar\":\"baz\"}", + )) + "\"}", + expected: `{ + "foo": "bar", + "custom_channel_data": { + "bar": "baz" + } +}`, + }, + { + name: "doesn't match pattern, returned identical", + data: "this ain't even json, and no custom data " + + "either", + expected: "this ain't even json, and no custom data " + + "either", + }, + { + name: "invalid json", + data: "this ain't json, " + + "\"custom_channel_data\":\"a\"", + expected: "this ain't json, " + + "\"custom_channel_data\":\"a\"", + }, + { + name: "valid json, invalid hex, just formatted", + data: "{\"custom_channel_data\":\"f\"}", + expected: "{\n \"custom_channel_data\": \"f\"\n}", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := replaceCustomData([]byte(tc.data)) + require.Equal(t, tc.expected, string(result)) + }) + } +} diff --git a/cmd/lncli/devrpc_active.go b/cmd/commands/devrpc_active.go similarity index 98% rename from cmd/lncli/devrpc_active.go rename to cmd/commands/devrpc_active.go index da3f08a97d..8d1960e461 100644 --- a/cmd/lncli/devrpc_active.go +++ b/cmd/commands/devrpc_active.go @@ -1,7 +1,7 @@ //go:build dev // +build dev -package main +package commands import ( "fmt" diff --git a/cmd/lncli/devrpc_default.go b/cmd/commands/devrpc_default.go similarity index 90% rename from cmd/lncli/devrpc_default.go rename to cmd/commands/devrpc_default.go index b9362cb421..1c5b482c32 100644 --- a/cmd/lncli/devrpc_default.go +++ b/cmd/commands/devrpc_default.go @@ -1,7 +1,7 @@ //go:build !dev // +build !dev -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/invoicesrpc_active.go b/cmd/commands/invoicesrpc_active.go similarity index 99% rename from cmd/lncli/invoicesrpc_active.go rename to cmd/commands/invoicesrpc_active.go index 823af67bf8..0ee767c8b2 100644 --- a/cmd/lncli/invoicesrpc_active.go +++ b/cmd/commands/invoicesrpc_active.go @@ -1,7 +1,7 @@ //go:build invoicesrpc // +build invoicesrpc -package main +package commands import ( "encoding/hex" diff --git a/cmd/lncli/invoicesrpc_default.go b/cmd/commands/invoicesrpc_default.go similarity index 92% rename from cmd/lncli/invoicesrpc_default.go rename to cmd/commands/invoicesrpc_default.go index cca3c14e9f..e925e55d69 100644 --- a/cmd/lncli/invoicesrpc_default.go +++ b/cmd/commands/invoicesrpc_default.go @@ -1,7 +1,7 @@ //go:build !invoicesrpc // +build !invoicesrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/macaroon_jar.go b/cmd/commands/macaroon_jar.go similarity index 99% rename from cmd/lncli/macaroon_jar.go rename to cmd/commands/macaroon_jar.go index f54f29a26c..d3a4345b0b 100644 --- a/cmd/lncli/macaroon_jar.go +++ b/cmd/commands/macaroon_jar.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/base64" diff --git a/cmd/lncli/macaroon_jar_test.go b/cmd/commands/macaroon_jar_test.go similarity index 99% rename from cmd/lncli/macaroon_jar_test.go rename to cmd/commands/macaroon_jar_test.go index 8e1d1c6bd4..6d76dce848 100644 --- a/cmd/lncli/macaroon_jar_test.go +++ b/cmd/commands/macaroon_jar_test.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" diff --git a/cmd/commands/main.go b/cmd/commands/main.go new file mode 100644 index 0000000000..71a8531d9a --- /dev/null +++ b/cmd/commands/main.go @@ -0,0 +1,601 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2016 The Decred developers +// Copyright (C) 2015-2024 The Lightning Network Developers + +package commands + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/build" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/tor" + "github.com/urfave/cli" + "golang.org/x/term" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" +) + +const ( + defaultDataDir = "data" + defaultChainSubDir = "chain" + defaultTLSCertFilename = "tls.cert" + defaultMacaroonFilename = "admin.macaroon" + defaultRPCPort = "10009" + defaultRPCHostPort = "localhost:" + defaultRPCPort + + envVarRPCServer = "LNCLI_RPCSERVER" + envVarLNDDir = "LNCLI_LNDDIR" + envVarSOCKSProxy = "LNCLI_SOCKSPROXY" + envVarTLSCertPath = "LNCLI_TLSCERTPATH" + envVarChain = "LNCLI_CHAIN" + envVarNetwork = "LNCLI_NETWORK" + envVarMacaroonPath = "LNCLI_MACAROONPATH" + envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT" + envVarMacaroonIP = "LNCLI_MACAROONIP" + envVarProfile = "LNCLI_PROFILE" + envVarMacFromJar = "LNCLI_MACFROMJAR" +) + +var ( + DefaultLndDir = btcutil.AppDataDir("lnd", false) + defaultTLSCertPath = filepath.Join( + DefaultLndDir, defaultTLSCertFilename, + ) + + // maxMsgRecvSize is the largest message our client will receive. We + // set this to 200MiB atm. + maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize) +) + +func fatal(err error) { + fmt.Fprintf(os.Stderr, "[lncli] %v\n", err) + os.Exit(1) +} + +func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient, + func()) { + + conn := getClientConn(ctx, true) + + cleanUp := func() { + conn.Close() + } + + return lnrpc.NewWalletUnlockerClient(conn), cleanUp +} + +func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) { + conn := getClientConn(ctx, true) + + cleanUp := func() { + conn.Close() + } + + return lnrpc.NewStateClient(conn), cleanUp +} + +func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) { + conn := getClientConn(ctx, false) + + cleanUp := func() { + conn.Close() + } + + return lnrpc.NewLightningClient(conn), cleanUp +} + +func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn { + // First, we'll get the selected stored profile or an ephemeral one + // created from the global options in the CLI context. + profile, err := getGlobalOptions(ctx, skipMacaroons) + if err != nil { + fatal(fmt.Errorf("could not load global options: %w", err)) + } + + // Create a dial options array. + opts := []grpc.DialOption{ + grpc.WithUnaryInterceptor( + addMetadataUnaryInterceptor(profile.Metadata), + ), + grpc.WithStreamInterceptor( + addMetaDataStreamInterceptor(profile.Metadata), + ), + } + + if profile.Insecure { + opts = append(opts, grpc.WithInsecure()) + } else { + // Load the specified TLS certificate. + certPool, err := profile.cert() + if err != nil { + fatal(fmt.Errorf("could not create cert pool: %w", err)) + } + + // Build transport credentials from the certificate pool. If + // there is no certificate pool, we expect the server to use a + // non-self-signed certificate such as a certificate obtained + // from Let's Encrypt. + var creds credentials.TransportCredentials + if certPool != nil { + creds = credentials.NewClientTLSFromCert(certPool, "") + } else { + // Fallback to the system pool. Using an empty tls + // config is an alternative to x509.SystemCertPool(). + // That call is not supported on Windows. + creds = credentials.NewTLS(&tls.Config{}) + } + + opts = append(opts, grpc.WithTransportCredentials(creds)) + } + + // Only process macaroon credentials if --no-macaroons isn't set and + // if we're not skipping macaroon processing. + if !profile.NoMacaroons && !skipMacaroons { + // Find out which macaroon to load. + macName := profile.Macaroons.Default + if ctx.GlobalIsSet("macfromjar") { + macName = ctx.GlobalString("macfromjar") + } + var macEntry *macaroonEntry + for _, entry := range profile.Macaroons.Jar { + if entry.Name == macName { + macEntry = entry + break + } + } + if macEntry == nil { + fatal(fmt.Errorf("macaroon with name '%s' not found "+ + "in profile", macName)) + } + + // Get and possibly decrypt the specified macaroon. + // + // TODO(guggero): Make it possible to cache the password so we + // don't need to ask for it every time. + mac, err := macEntry.loadMacaroon(readPassword) + if err != nil { + fatal(fmt.Errorf("could not load macaroon: %w", err)) + } + + macConstraints := []macaroons.Constraint{ + // We add a time-based constraint to prevent replay of + // the macaroon. It's good for 60 seconds by default to + // make up for any discrepancy between client and server + // clocks, but leaking the macaroon before it becomes + // invalid makes it possible for an attacker to reuse + // the macaroon. In addition, the validity time of the + // macaroon is extended by the time the server clock is + // behind the client clock, or shortened by the time the + // server clock is ahead of the client clock (or invalid + // altogether if, in the latter case, this time is more + // than 60 seconds). + // TODO(aakselrod): add better anti-replay protection. + macaroons.TimeoutConstraint(profile.Macaroons.Timeout), + + // Lock macaroon down to a specific IP address. + macaroons.IPLockConstraint(profile.Macaroons.IP), + + // ... Add more constraints if needed. + } + + // Apply constraints to the macaroon. + constrainedMac, err := macaroons.AddConstraints( + mac, macConstraints..., + ) + if err != nil { + fatal(err) + } + + // Now we append the macaroon credentials to the dial options. + cred, err := macaroons.NewMacaroonCredential(constrainedMac) + if err != nil { + fatal(fmt.Errorf("error cloning mac: %w", err)) + } + opts = append(opts, grpc.WithPerRPCCredentials(cred)) + } + + // If a socksproxy server is specified we use a tor dialer + // to connect to the grpc server. + if ctx.GlobalIsSet("socksproxy") { + socksProxy := ctx.GlobalString("socksproxy") + torDialer := func(_ context.Context, addr string) (net.Conn, + error) { + + return tor.Dial( + addr, socksProxy, false, false, + tor.DefaultConnTimeout, + ) + } + opts = append(opts, grpc.WithContextDialer(torDialer)) + } else { + // We need to use a custom dialer so we can also connect to + // unix sockets and not just TCP addresses. + genericDialer := lncfg.ClientAddressDialer(defaultRPCPort) + opts = append(opts, grpc.WithContextDialer(genericDialer)) + } + + opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize)) + + conn, err := grpc.Dial(profile.RPCServer, opts...) + if err != nil { + fatal(fmt.Errorf("unable to connect to RPC server: %w", err)) + } + + return conn +} + +// addMetadataUnaryInterceptor returns a grpc client side interceptor that +// appends any key-value metadata strings to the outgoing context of a grpc +// unary call. +func addMetadataUnaryInterceptor( + md map[string]string) grpc.UnaryClientInterceptor { + + return func(ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, + opts ...grpc.CallOption) error { + + outCtx := contextWithMetadata(ctx, md) + return invoker(outCtx, method, req, reply, cc, opts...) + } +} + +// addMetaDataStreamInterceptor returns a grpc client side interceptor that +// appends any key-value metadata strings to the outgoing context of a grpc +// stream call. +func addMetaDataStreamInterceptor( + md map[string]string) grpc.StreamClientInterceptor { + + return func(ctx context.Context, desc *grpc.StreamDesc, + cc *grpc.ClientConn, method string, streamer grpc.Streamer, + opts ...grpc.CallOption) (grpc.ClientStream, error) { + + outCtx := contextWithMetadata(ctx, md) + return streamer(outCtx, desc, cc, method, opts...) + } +} + +// contextWithMetaData appends the given metadata key-value pairs to the given +// context. +func contextWithMetadata(ctx context.Context, + md map[string]string) context.Context { + + kvPairs := make([]string, 0, 2*len(md)) + for k, v := range md { + kvPairs = append(kvPairs, k, v) + } + + return metadata.AppendToOutgoingContext(ctx, kvPairs...) +} + +// extractPathArgs parses the TLS certificate and macaroon paths from the +// command. +func extractPathArgs(ctx *cli.Context) (string, string, error) { + network := strings.ToLower(ctx.GlobalString("network")) + switch network { + case "mainnet", "testnet", "regtest", "simnet", "signet": + default: + return "", "", fmt.Errorf("unknown network: %v", network) + } + + // We'll now fetch the lnddir so we can make a decision on how to + // properly read the macaroons (if needed) and also the cert. This will + // either be the default, or will have been overwritten by the end + // user. + lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir")) + + // If the macaroon path as been manually provided, then we'll only + // target the specified file. + var macPath string + if ctx.GlobalString("macaroonpath") != "" { + macPath = lncfg.CleanAndExpandPath(ctx.GlobalString( + "macaroonpath", + )) + } else { + // Otherwise, we'll go into the path: + // lnddir/data/chain// in order to fetch the + // macaroon that we need. + macPath = filepath.Join( + lndDir, defaultDataDir, defaultChainSubDir, + lnd.BitcoinChainName, network, defaultMacaroonFilename, + ) + } + + tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath")) + + // If a custom lnd directory was set, we'll also check if custom paths + // for the TLS cert and macaroon file were set as well. If not, we'll + // override their paths so they can be found within the custom lnd + // directory set. This allows us to set a custom lnd directory, along + // with custom paths to the TLS cert and macaroon file. + if lndDir != DefaultLndDir { + tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename) + } + + return tlsCertPath, macPath, nil +} + +// checkNotBothSet accepts two flag names, a and b, and checks that only flag a +// or flag b can be set, but not both. It returns the name of the flag or an +// error. +func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) { + if ctx.IsSet(a) && ctx.IsSet(b) { + return "", fmt.Errorf( + "either %s or %s should be set, but not both", a, b, + ) + } + + if ctx.IsSet(a) { + return a, nil + } + + return b, nil +} + +func Main() { + app := cli.NewApp() + app.Name = "lncli" + app.Version = build.Version() + " commit=" + build.Commit + app.Usage = "control plane for your Lightning Network Daemon (lnd)" + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "rpcserver", + Value: defaultRPCHostPort, + Usage: "The host:port of LN daemon.", + EnvVar: envVarRPCServer, + }, + cli.StringFlag{ + Name: "lnddir", + Value: DefaultLndDir, + Usage: "The path to lnd's base directory.", + TakesFile: true, + EnvVar: envVarLNDDir, + }, + cli.StringFlag{ + Name: "socksproxy", + Usage: "The host:port of a SOCKS proxy through " + + "which all connections to the LN " + + "daemon will be established over.", + EnvVar: envVarSOCKSProxy, + }, + cli.StringFlag{ + Name: "tlscertpath", + Value: defaultTLSCertPath, + Usage: "The path to lnd's TLS certificate.", + TakesFile: true, + EnvVar: envVarTLSCertPath, + }, + cli.StringFlag{ + Name: "chain, c", + Usage: "The chain lnd is running on, e.g. bitcoin.", + Value: "bitcoin", + EnvVar: envVarChain, + }, + cli.StringFlag{ + Name: "network, n", + Usage: "The network lnd is running on, e.g. mainnet, " + + "testnet, etc.", + Value: "mainnet", + EnvVar: envVarNetwork, + }, + cli.BoolFlag{ + Name: "no-macaroons", + Usage: "Disable macaroon authentication.", + }, + cli.StringFlag{ + Name: "macaroonpath", + Usage: "The path to macaroon file.", + TakesFile: true, + EnvVar: envVarMacaroonPath, + }, + cli.Int64Flag{ + Name: "macaroontimeout", + Value: 60, + Usage: "Anti-replay macaroon validity time in " + + "seconds.", + EnvVar: envVarMacaroonTimeout, + }, + cli.StringFlag{ + Name: "macaroonip", + Usage: "If set, lock macaroon to specific IP address.", + EnvVar: envVarMacaroonIP, + }, + cli.StringFlag{ + Name: "profile, p", + Usage: "Instead of reading settings from command " + + "line parameters or using the default " + + "profile, use a specific profile. If " + + "a default profile is set, this flag can be " + + "set to an empty string to disable reading " + + "values from the profiles file.", + EnvVar: envVarProfile, + }, + cli.StringFlag{ + Name: "macfromjar", + Usage: "Use this macaroon from the profile's " + + "macaroon jar instead of the default one. " + + "Can only be used if profiles are defined.", + EnvVar: envVarMacFromJar, + }, + cli.StringSliceFlag{ + Name: "metadata", + Usage: "This flag can be used to specify a key-value " + + "pair that should be appended to the " + + "outgoing context before the request is sent " + + "to lnd. This flag may be specified multiple " + + "times. The format is: \"key:value\".", + }, + cli.BoolFlag{ + Name: "insecure", + Usage: "Connect to the rpc server without TLS " + + "authentication", + Hidden: true, + }, + } + app.Commands = []cli.Command{ + createCommand, + createWatchOnlyCommand, + unlockCommand, + changePasswordCommand, + newAddressCommand, + estimateFeeCommand, + sendManyCommand, + sendCoinsCommand, + listUnspentCommand, + connectCommand, + disconnectCommand, + openChannelCommand, + batchOpenChannelCommand, + closeChannelCommand, + closeAllChannelsCommand, + abandonChannelCommand, + listPeersCommand, + walletBalanceCommand, + ChannelBalanceCommand, + getInfoCommand, + getDebugInfoCommand, + encryptDebugPackageCommand, + decryptDebugPackageCommand, + getRecoveryInfoCommand, + pendingChannelsCommand, + SendPaymentCommand, + payInvoiceCommand, + sendToRouteCommand, + AddInvoiceCommand, + lookupInvoiceCommand, + listInvoicesCommand, + ListChannelsCommand, + closedChannelsCommand, + listPaymentsCommand, + describeGraphCommand, + getNodeMetricsCommand, + getChanInfoCommand, + getNodeInfoCommand, + queryRoutesCommand, + getNetworkInfoCommand, + debugLevelCommand, + decodePayReqCommand, + listChainTxnsCommand, + stopCommand, + signMessageCommand, + verifyMessageCommand, + feeReportCommand, + updateChannelPolicyCommand, + forwardingHistoryCommand, + exportChanBackupCommand, + verifyChanBackupCommand, + restoreChanBackupCommand, + bakeMacaroonCommand, + listMacaroonIDsCommand, + deleteMacaroonIDCommand, + listPermissionsCommand, + printMacaroonCommand, + constrainMacaroonCommand, + trackPaymentCommand, + versionCommand, + profileSubCommand, + getStateCommand, + deletePaymentsCommand, + sendCustomCommand, + subscribeCustomCommand, + fishCompletionCommand, + listAliasesCommand, + estimateRouteFeeCommand, + generateManPageCommand, + } + + // Add any extra commands determined by build flags. + app.Commands = append(app.Commands, autopilotCommands()...) + app.Commands = append(app.Commands, invoicesCommands()...) + app.Commands = append(app.Commands, neutrinoCommands()...) + app.Commands = append(app.Commands, routerCommands()...) + app.Commands = append(app.Commands, walletCommands()...) + app.Commands = append(app.Commands, watchtowerCommands()...) + app.Commands = append(app.Commands, wtclientCommands()...) + app.Commands = append(app.Commands, devCommands()...) + app.Commands = append(app.Commands, peersCommands()...) + app.Commands = append(app.Commands, chainCommands()...) + + if err := app.Run(os.Args); err != nil { + fatal(err) + } +} + +// readPassword reads a password from the terminal. This requires there to be an +// actual TTY so passing in a password from stdin won't work. +func readPassword(text string) ([]byte, error) { + fmt.Print(text) + + // The variable syscall.Stdin is of a different type in the Windows API + // that's why we need the explicit cast. And of course the linter + // doesn't like it either. + pw, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert + fmt.Println() + + return pw, err +} + +// networkParams parses the global network flag into a chaincfg.Params. +func networkParams(ctx *cli.Context) (*chaincfg.Params, error) { + network := strings.ToLower(ctx.GlobalString("network")) + switch network { + case "mainnet": + return &chaincfg.MainNetParams, nil + + case "testnet": + return &chaincfg.TestNet3Params, nil + + case "regtest": + return &chaincfg.RegressionNetParams, nil + + case "simnet": + return &chaincfg.SimNetParams, nil + + case "signet": + return &chaincfg.SigNetParams, nil + + default: + return nil, fmt.Errorf("unknown network: %v", network) + } +} + +// parseCoinSelectionStrategy parses a coin selection strategy string +// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type. +func parseCoinSelectionStrategy(ctx *cli.Context) ( + lnrpc.CoinSelectionStrategy, error) { + + strategy := ctx.String(coinSelectionStrategyFlag.Name) + if !ctx.IsSet(coinSelectionStrategyFlag.Name) { + return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, + nil + } + + switch strategy { + case "global-config": + return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, + nil + + case "largest": + return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil + + case "random": + return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil + + default: + return 0, fmt.Errorf("unknown coin selection strategy "+ + "%v", strategy) + } +} diff --git a/cmd/lncli/neutrino_active.go b/cmd/commands/neutrino_active.go similarity index 99% rename from cmd/lncli/neutrino_active.go rename to cmd/commands/neutrino_active.go index 099da46c6e..f34c7cc0e2 100644 --- a/cmd/lncli/neutrino_active.go +++ b/cmd/commands/neutrino_active.go @@ -1,7 +1,7 @@ //go:build neutrinorpc // +build neutrinorpc -package main +package commands import ( "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" diff --git a/cmd/lncli/neutrino_default.go b/cmd/commands/neutrino_default.go similarity index 92% rename from cmd/lncli/neutrino_default.go rename to cmd/commands/neutrino_default.go index f1f1de404b..b269e12386 100644 --- a/cmd/lncli/neutrino_default.go +++ b/cmd/commands/neutrino_default.go @@ -1,7 +1,7 @@ //go:build !neutrinorpc // +build !neutrinorpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/peersrpc_active.go b/cmd/commands/peersrpc_active.go similarity index 99% rename from cmd/lncli/peersrpc_active.go rename to cmd/commands/peersrpc_active.go index c044166d36..0736750c73 100644 --- a/cmd/lncli/peersrpc_active.go +++ b/cmd/commands/peersrpc_active.go @@ -1,7 +1,7 @@ //go:build peersrpc // +build peersrpc -package main +package commands import ( "fmt" diff --git a/cmd/lncli/peersrpc_default.go b/cmd/commands/peersrpc_default.go similarity index 91% rename from cmd/lncli/peersrpc_default.go rename to cmd/commands/peersrpc_default.go index 24cb2b8134..57c8aa7a97 100644 --- a/cmd/lncli/peersrpc_default.go +++ b/cmd/commands/peersrpc_default.go @@ -1,7 +1,7 @@ //go:build !peersrpc // +build !peersrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/profile.go b/cmd/commands/profile.go similarity index 99% rename from cmd/lncli/profile.go rename to cmd/commands/profile.go index efbdfc147a..0d63ca93c9 100644 --- a/cmd/lncli/profile.go +++ b/cmd/commands/profile.go @@ -1,4 +1,4 @@ -package main +package commands import ( "bytes" diff --git a/cmd/lncli/routerrpc.go b/cmd/commands/routerrpc.go similarity index 95% rename from cmd/lncli/routerrpc.go rename to cmd/commands/routerrpc.go index 30b8922249..82211affdf 100644 --- a/cmd/lncli/routerrpc.go +++ b/cmd/commands/routerrpc.go @@ -1,4 +1,4 @@ -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/types.go b/cmd/commands/types.go similarity index 99% rename from cmd/lncli/types.go rename to cmd/commands/types.go index a93d3e2c68..2a82e7100f 100644 --- a/cmd/lncli/types.go +++ b/cmd/commands/types.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" diff --git a/cmd/lncli/walletrpc_active.go b/cmd/commands/walletrpc_active.go similarity index 99% rename from cmd/lncli/walletrpc_active.go rename to cmd/commands/walletrpc_active.go index 5fd4830e28..f4b7039a89 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/commands/walletrpc_active.go @@ -1,7 +1,7 @@ //go:build walletrpc // +build walletrpc -package main +package commands import ( "bytes" diff --git a/cmd/lncli/walletrpc_default.go b/cmd/commands/walletrpc_default.go similarity index 91% rename from cmd/lncli/walletrpc_default.go rename to cmd/commands/walletrpc_default.go index d6670e4499..90c627c2a5 100644 --- a/cmd/lncli/walletrpc_default.go +++ b/cmd/commands/walletrpc_default.go @@ -1,7 +1,7 @@ //go:build !walletrpc // +build !walletrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/walletrpc_types.go b/cmd/commands/walletrpc_types.go similarity index 99% rename from cmd/lncli/walletrpc_types.go rename to cmd/commands/walletrpc_types.go index ab251d18f2..4369151fad 100644 --- a/cmd/lncli/walletrpc_types.go +++ b/cmd/commands/walletrpc_types.go @@ -1,4 +1,4 @@ -package main +package commands import "github.com/lightningnetwork/lnd/lnrpc/walletrpc" diff --git a/cmd/lncli/watchtower_active.go b/cmd/commands/watchtower_active.go similarity index 98% rename from cmd/lncli/watchtower_active.go rename to cmd/commands/watchtower_active.go index 9c31c6ec4b..bc5cd19695 100644 --- a/cmd/lncli/watchtower_active.go +++ b/cmd/commands/watchtower_active.go @@ -1,7 +1,7 @@ //go:build watchtowerrpc // +build watchtowerrpc -package main +package commands import ( "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" diff --git a/cmd/lncli/watchtower_default.go b/cmd/commands/watchtower_default.go similarity index 92% rename from cmd/lncli/watchtower_default.go rename to cmd/commands/watchtower_default.go index e3db3ccf36..c958a66bdc 100644 --- a/cmd/lncli/watchtower_default.go +++ b/cmd/commands/watchtower_default.go @@ -1,7 +1,7 @@ //go:build !watchtowerrpc // +build !watchtowerrpc -package main +package commands import "github.com/urfave/cli" diff --git a/cmd/lncli/wtclient.go b/cmd/commands/wtclient.go similarity index 99% rename from cmd/lncli/wtclient.go rename to cmd/commands/wtclient.go index d73f6ca612..075861b981 100644 --- a/cmd/lncli/wtclient.go +++ b/cmd/commands/wtclient.go @@ -1,4 +1,4 @@ -package main +package commands import ( "encoding/hex" diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index b1554fb070..ea5195a0c7 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -1,594 +1,11 @@ // Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers -// Copyright (C) 2015-2022 The Lightning Network Developers +// Copyright (C) 2015-2024 The Lightning Network Developers package main -import ( - "context" - "crypto/tls" - "fmt" - "net" - "os" - "path/filepath" - "strings" - "syscall" - - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/lightningnetwork/lnd" - "github.com/lightningnetwork/lnd/build" - "github.com/lightningnetwork/lnd/lncfg" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/macaroons" - "github.com/lightningnetwork/lnd/tor" - "github.com/urfave/cli" - "golang.org/x/term" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/metadata" -) - -const ( - defaultDataDir = "data" - defaultChainSubDir = "chain" - defaultTLSCertFilename = "tls.cert" - defaultMacaroonFilename = "admin.macaroon" - defaultRPCPort = "10009" - defaultRPCHostPort = "localhost:" + defaultRPCPort - - envVarRPCServer = "LNCLI_RPCSERVER" - envVarLNDDir = "LNCLI_LNDDIR" - envVarSOCKSProxy = "LNCLI_SOCKSPROXY" - envVarTLSCertPath = "LNCLI_TLSCERTPATH" - envVarChain = "LNCLI_CHAIN" - envVarNetwork = "LNCLI_NETWORK" - envVarMacaroonPath = "LNCLI_MACAROONPATH" - envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT" - envVarMacaroonIP = "LNCLI_MACAROONIP" - envVarProfile = "LNCLI_PROFILE" - envVarMacFromJar = "LNCLI_MACFROMJAR" -) - -var ( - defaultLndDir = btcutil.AppDataDir("lnd", false) - defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename) - - // maxMsgRecvSize is the largest message our client will receive. We - // set this to 200MiB atm. - maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize) -) - -func fatal(err error) { - fmt.Fprintf(os.Stderr, "[lncli] %v\n", err) - os.Exit(1) -} - -func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient, func()) { - conn := getClientConn(ctx, true) - - cleanUp := func() { - conn.Close() - } - - return lnrpc.NewWalletUnlockerClient(conn), cleanUp -} - -func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) { - conn := getClientConn(ctx, true) - - cleanUp := func() { - conn.Close() - } - - return lnrpc.NewStateClient(conn), cleanUp -} - -func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) { - conn := getClientConn(ctx, false) - - cleanUp := func() { - conn.Close() - } - - return lnrpc.NewLightningClient(conn), cleanUp -} - -func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn { - // First, we'll get the selected stored profile or an ephemeral one - // created from the global options in the CLI context. - profile, err := getGlobalOptions(ctx, skipMacaroons) - if err != nil { - fatal(fmt.Errorf("could not load global options: %w", err)) - } - - // Create a dial options array. - opts := []grpc.DialOption{ - grpc.WithUnaryInterceptor( - addMetadataUnaryInterceptor(profile.Metadata), - ), - grpc.WithStreamInterceptor( - addMetaDataStreamInterceptor(profile.Metadata), - ), - } - - if profile.Insecure { - opts = append(opts, grpc.WithInsecure()) - } else { - // Load the specified TLS certificate. - certPool, err := profile.cert() - if err != nil { - fatal(fmt.Errorf("could not create cert pool: %w", err)) - } - - // Build transport credentials from the certificate pool. If - // there is no certificate pool, we expect the server to use a - // non-self-signed certificate such as a certificate obtained - // from Let's Encrypt. - var creds credentials.TransportCredentials - if certPool != nil { - creds = credentials.NewClientTLSFromCert(certPool, "") - } else { - // Fallback to the system pool. Using an empty tls - // config is an alternative to x509.SystemCertPool(). - // That call is not supported on Windows. - creds = credentials.NewTLS(&tls.Config{}) - } - - opts = append(opts, grpc.WithTransportCredentials(creds)) - } - - // Only process macaroon credentials if --no-macaroons isn't set and - // if we're not skipping macaroon processing. - if !profile.NoMacaroons && !skipMacaroons { - // Find out which macaroon to load. - macName := profile.Macaroons.Default - if ctx.GlobalIsSet("macfromjar") { - macName = ctx.GlobalString("macfromjar") - } - var macEntry *macaroonEntry - for _, entry := range profile.Macaroons.Jar { - if entry.Name == macName { - macEntry = entry - break - } - } - if macEntry == nil { - fatal(fmt.Errorf("macaroon with name '%s' not found "+ - "in profile", macName)) - } - - // Get and possibly decrypt the specified macaroon. - // - // TODO(guggero): Make it possible to cache the password so we - // don't need to ask for it every time. - mac, err := macEntry.loadMacaroon(readPassword) - if err != nil { - fatal(fmt.Errorf("could not load macaroon: %w", err)) - } - - macConstraints := []macaroons.Constraint{ - // We add a time-based constraint to prevent replay of - // the macaroon. It's good for 60 seconds by default to - // make up for any discrepancy between client and server - // clocks, but leaking the macaroon before it becomes - // invalid makes it possible for an attacker to reuse - // the macaroon. In addition, the validity time of the - // macaroon is extended by the time the server clock is - // behind the client clock, or shortened by the time the - // server clock is ahead of the client clock (or invalid - // altogether if, in the latter case, this time is more - // than 60 seconds). - // TODO(aakselrod): add better anti-replay protection. - macaroons.TimeoutConstraint(profile.Macaroons.Timeout), - - // Lock macaroon down to a specific IP address. - macaroons.IPLockConstraint(profile.Macaroons.IP), - - // ... Add more constraints if needed. - } - - // Apply constraints to the macaroon. - constrainedMac, err := macaroons.AddConstraints( - mac, macConstraints..., - ) - if err != nil { - fatal(err) - } - - // Now we append the macaroon credentials to the dial options. - cred, err := macaroons.NewMacaroonCredential(constrainedMac) - if err != nil { - fatal(fmt.Errorf("error cloning mac: %w", err)) - } - opts = append(opts, grpc.WithPerRPCCredentials(cred)) - } - - // If a socksproxy server is specified we use a tor dialer - // to connect to the grpc server. - if ctx.GlobalIsSet("socksproxy") { - socksProxy := ctx.GlobalString("socksproxy") - torDialer := func(_ context.Context, addr string) (net.Conn, - error) { - - return tor.Dial( - addr, socksProxy, false, false, - tor.DefaultConnTimeout, - ) - } - opts = append(opts, grpc.WithContextDialer(torDialer)) - } else { - // We need to use a custom dialer so we can also connect to - // unix sockets and not just TCP addresses. - genericDialer := lncfg.ClientAddressDialer(defaultRPCPort) - opts = append(opts, grpc.WithContextDialer(genericDialer)) - } - - opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize)) - - conn, err := grpc.Dial(profile.RPCServer, opts...) - if err != nil { - fatal(fmt.Errorf("unable to connect to RPC server: %w", err)) - } - - return conn -} - -// addMetadataUnaryInterceptor returns a grpc client side interceptor that -// appends any key-value metadata strings to the outgoing context of a grpc -// unary call. -func addMetadataUnaryInterceptor( - md map[string]string) grpc.UnaryClientInterceptor { - - return func(ctx context.Context, method string, req, reply interface{}, - cc *grpc.ClientConn, invoker grpc.UnaryInvoker, - opts ...grpc.CallOption) error { - - outCtx := contextWithMetadata(ctx, md) - return invoker(outCtx, method, req, reply, cc, opts...) - } -} - -// addMetaDataStreamInterceptor returns a grpc client side interceptor that -// appends any key-value metadata strings to the outgoing context of a grpc -// stream call. -func addMetaDataStreamInterceptor( - md map[string]string) grpc.StreamClientInterceptor { - - return func(ctx context.Context, desc *grpc.StreamDesc, - cc *grpc.ClientConn, method string, streamer grpc.Streamer, - opts ...grpc.CallOption) (grpc.ClientStream, error) { - - outCtx := contextWithMetadata(ctx, md) - return streamer(outCtx, desc, cc, method, opts...) - } -} - -// contextWithMetaData appends the given metadata key-value pairs to the given -// context. -func contextWithMetadata(ctx context.Context, - md map[string]string) context.Context { - - kvPairs := make([]string, 0, 2*len(md)) - for k, v := range md { - kvPairs = append(kvPairs, k, v) - } - - return metadata.AppendToOutgoingContext(ctx, kvPairs...) -} - -// extractPathArgs parses the TLS certificate and macaroon paths from the -// command. -func extractPathArgs(ctx *cli.Context) (string, string, error) { - network := strings.ToLower(ctx.GlobalString("network")) - switch network { - case "mainnet", "testnet", "regtest", "simnet", "signet": - default: - return "", "", fmt.Errorf("unknown network: %v", network) - } - - // We'll now fetch the lnddir so we can make a decision on how to - // properly read the macaroons (if needed) and also the cert. This will - // either be the default, or will have been overwritten by the end - // user. - lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir")) - - // If the macaroon path as been manually provided, then we'll only - // target the specified file. - var macPath string - if ctx.GlobalString("macaroonpath") != "" { - macPath = lncfg.CleanAndExpandPath(ctx.GlobalString("macaroonpath")) - } else { - // Otherwise, we'll go into the path: - // lnddir/data/chain// in order to fetch the - // macaroon that we need. - macPath = filepath.Join( - lndDir, defaultDataDir, defaultChainSubDir, - lnd.BitcoinChainName, network, defaultMacaroonFilename, - ) - } - - tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath")) - - // If a custom lnd directory was set, we'll also check if custom paths - // for the TLS cert and macaroon file were set as well. If not, we'll - // override their paths so they can be found within the custom lnd - // directory set. This allows us to set a custom lnd directory, along - // with custom paths to the TLS cert and macaroon file. - if lndDir != defaultLndDir { - tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename) - } - - return tlsCertPath, macPath, nil -} - -// checkNotBothSet accepts two flag names, a and b, and checks that only flag a -// or flag b can be set, but not both. It returns the name of the flag or an -// error. -func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) { - if ctx.IsSet(a) && ctx.IsSet(b) { - return "", fmt.Errorf( - "either %s or %s should be set, but not both", a, b, - ) - } - - if ctx.IsSet(a) { - return a, nil - } - - return b, nil -} +import "github.com/lightningnetwork/lnd/cmd/commands" func main() { - app := cli.NewApp() - app.Name = "lncli" - app.Version = build.Version() + " commit=" + build.Commit - app.Usage = "control plane for your Lightning Network Daemon (lnd)" - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "rpcserver", - Value: defaultRPCHostPort, - Usage: "The host:port of LN daemon.", - EnvVar: envVarRPCServer, - }, - cli.StringFlag{ - Name: "lnddir", - Value: defaultLndDir, - Usage: "The path to lnd's base directory.", - TakesFile: true, - EnvVar: envVarLNDDir, - }, - cli.StringFlag{ - Name: "socksproxy", - Usage: "The host:port of a SOCKS proxy through " + - "which all connections to the LN " + - "daemon will be established over.", - EnvVar: envVarSOCKSProxy, - }, - cli.StringFlag{ - Name: "tlscertpath", - Value: defaultTLSCertPath, - Usage: "The path to lnd's TLS certificate.", - TakesFile: true, - EnvVar: envVarTLSCertPath, - }, - cli.StringFlag{ - Name: "chain, c", - Usage: "The chain lnd is running on, e.g. bitcoin.", - Value: "bitcoin", - EnvVar: envVarChain, - }, - cli.StringFlag{ - Name: "network, n", - Usage: "The network lnd is running on, e.g. mainnet, " + - "testnet, etc.", - Value: "mainnet", - EnvVar: envVarNetwork, - }, - cli.BoolFlag{ - Name: "no-macaroons", - Usage: "Disable macaroon authentication.", - }, - cli.StringFlag{ - Name: "macaroonpath", - Usage: "The path to macaroon file.", - TakesFile: true, - EnvVar: envVarMacaroonPath, - }, - cli.Int64Flag{ - Name: "macaroontimeout", - Value: 60, - Usage: "Anti-replay macaroon validity time in " + - "seconds.", - EnvVar: envVarMacaroonTimeout, - }, - cli.StringFlag{ - Name: "macaroonip", - Usage: "If set, lock macaroon to specific IP address.", - EnvVar: envVarMacaroonIP, - }, - cli.StringFlag{ - Name: "profile, p", - Usage: "Instead of reading settings from command " + - "line parameters or using the default " + - "profile, use a specific profile. If " + - "a default profile is set, this flag can be " + - "set to an empty string to disable reading " + - "values from the profiles file.", - EnvVar: envVarProfile, - }, - cli.StringFlag{ - Name: "macfromjar", - Usage: "Use this macaroon from the profile's " + - "macaroon jar instead of the default one. " + - "Can only be used if profiles are defined.", - EnvVar: envVarMacFromJar, - }, - cli.StringSliceFlag{ - Name: "metadata", - Usage: "This flag can be used to specify a key-value " + - "pair that should be appended to the " + - "outgoing context before the request is sent " + - "to lnd. This flag may be specified multiple " + - "times. The format is: \"key:value\".", - }, - cli.BoolFlag{ - Name: "insecure", - Usage: "Connect to the rpc server without TLS " + - "authentication", - Hidden: true, - }, - } - app.Commands = []cli.Command{ - createCommand, - createWatchOnlyCommand, - unlockCommand, - changePasswordCommand, - newAddressCommand, - estimateFeeCommand, - sendManyCommand, - sendCoinsCommand, - listUnspentCommand, - connectCommand, - disconnectCommand, - openChannelCommand, - batchOpenChannelCommand, - closeChannelCommand, - closeAllChannelsCommand, - abandonChannelCommand, - listPeersCommand, - walletBalanceCommand, - channelBalanceCommand, - getInfoCommand, - getDebugInfoCommand, - encryptDebugPackageCommand, - decryptDebugPackageCommand, - getRecoveryInfoCommand, - pendingChannelsCommand, - sendPaymentCommand, - payInvoiceCommand, - sendToRouteCommand, - addInvoiceCommand, - lookupInvoiceCommand, - listInvoicesCommand, - listChannelsCommand, - closedChannelsCommand, - listPaymentsCommand, - describeGraphCommand, - getNodeMetricsCommand, - getChanInfoCommand, - getNodeInfoCommand, - queryRoutesCommand, - getNetworkInfoCommand, - debugLevelCommand, - decodePayReqCommand, - listChainTxnsCommand, - stopCommand, - signMessageCommand, - verifyMessageCommand, - feeReportCommand, - updateChannelPolicyCommand, - forwardingHistoryCommand, - exportChanBackupCommand, - verifyChanBackupCommand, - restoreChanBackupCommand, - bakeMacaroonCommand, - listMacaroonIDsCommand, - deleteMacaroonIDCommand, - listPermissionsCommand, - printMacaroonCommand, - constrainMacaroonCommand, - trackPaymentCommand, - versionCommand, - profileSubCommand, - getStateCommand, - deletePaymentsCommand, - sendCustomCommand, - subscribeCustomCommand, - fishCompletionCommand, - listAliasesCommand, - estimateRouteFeeCommand, - generateManPageCommand, - } - - // Add any extra commands determined by build flags. - app.Commands = append(app.Commands, autopilotCommands()...) - app.Commands = append(app.Commands, invoicesCommands()...) - app.Commands = append(app.Commands, neutrinoCommands()...) - app.Commands = append(app.Commands, routerCommands()...) - app.Commands = append(app.Commands, walletCommands()...) - app.Commands = append(app.Commands, watchtowerCommands()...) - app.Commands = append(app.Commands, wtclientCommands()...) - app.Commands = append(app.Commands, devCommands()...) - app.Commands = append(app.Commands, peersCommands()...) - app.Commands = append(app.Commands, chainCommands()...) - - if err := app.Run(os.Args); err != nil { - fatal(err) - } -} - -// readPassword reads a password from the terminal. This requires there to be an -// actual TTY so passing in a password from stdin won't work. -func readPassword(text string) ([]byte, error) { - fmt.Print(text) - - // The variable syscall.Stdin is of a different type in the Windows API - // that's why we need the explicit cast. And of course the linter - // doesn't like it either. - pw, err := term.ReadPassword(int(syscall.Stdin)) // nolint:unconvert - fmt.Println() - return pw, err -} - -// networkParams parses the global network flag into a chaincfg.Params. -func networkParams(ctx *cli.Context) (*chaincfg.Params, error) { - network := strings.ToLower(ctx.GlobalString("network")) - switch network { - case "mainnet": - return &chaincfg.MainNetParams, nil - - case "testnet": - return &chaincfg.TestNet3Params, nil - - case "regtest": - return &chaincfg.RegressionNetParams, nil - - case "simnet": - return &chaincfg.SimNetParams, nil - - case "signet": - return &chaincfg.SigNetParams, nil - - default: - return nil, fmt.Errorf("unknown network: %v", network) - } -} - -// parseCoinSelectionStrategy parses a coin selection strategy string -// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type. -func parseCoinSelectionStrategy(ctx *cli.Context) ( - lnrpc.CoinSelectionStrategy, error) { - - strategy := ctx.String(coinSelectionStrategyFlag.Name) - if !ctx.IsSet(coinSelectionStrategyFlag.Name) { - return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, - nil - } - - switch strategy { - case "global-config": - return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, - nil - - case "largest": - return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil - - case "random": - return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil - - default: - return 0, fmt.Errorf("unknown coin selection strategy "+ - "%v", strategy) - } + commands.Main() } diff --git a/config_builder.go b/config_builder.go index bf6274cdf5..1c3a842ef1 100644 --- a/config_builder.go +++ b/config_builder.go @@ -33,6 +33,8 @@ import ( "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -40,11 +42,15 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" + "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwallet/rpcwallet" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/msgmux" + "github.com/lightningnetwork/lnd/routing" "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" @@ -103,7 +109,7 @@ type DatabaseBuilder interface { type WalletConfigBuilder interface { // BuildWalletConfig is responsible for creating or unlocking and then // fully initializing a wallet. - BuildWalletConfig(context.Context, *DatabaseInstances, + BuildWalletConfig(context.Context, *DatabaseInstances, *AuxComponents, *rpcperms.InterceptorChain, []*ListenerWithSignal) (*chainreg.PartialChainControl, *btcwallet.Config, func(), error) @@ -144,6 +150,52 @@ type ImplementationCfg struct { // ChainControlBuilder is a type that can provide a custom wallet // implementation. ChainControlBuilder + + // AuxComponents is a set of auxiliary components that can be used by + // lnd for certain custom channel types. + AuxComponents +} + +// AuxComponents is a set of auxiliary components that can be used by lnd for +// certain custom channel types. +type AuxComponents struct { + // AuxLeafStore is an optional data source that can be used by custom + // channels to fetch+store various data. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // TrafficShaper is an optional traffic shaper that can be used to + // control the outgoing channel of a payment. + TrafficShaper fn.Option[routing.TlvTrafficShaper] + + // MsgRouter is an optional message router that if set will be used in + // place of a new blank default message router. + MsgRouter fn.Option[msgmux.Router] + + // AuxFundingController is an optional controller that can be used to + // modify the way we handle certain custom channel types. It's also + // able to automatically handle new custom protocol messages related to + // the funding process. + AuxFundingController fn.Option[funding.AuxFundingController] + + // AuxSigner is an optional signer that can be used to sign auxiliary + // leaves for certain custom channel types. + AuxSigner fn.Option[lnwallet.AuxSigner] + + // AuxDataParser is an optional data parser that can be used to parse + // auxiliary data for certain custom channel types. + AuxDataParser fn.Option[AuxDataParser] + + // 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 @@ -228,7 +280,8 @@ func (d *DefaultWalletImpl) Permissions() map[string][]bakery.Op { // // NOTE: This is part of the WalletConfigBuilder interface. func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, - dbs *DatabaseInstances, interceptorChain *rpcperms.InterceptorChain, + dbs *DatabaseInstances, aux *AuxComponents, + interceptorChain *rpcperms.InterceptorChain, grpcListeners []*ListenerWithSignal) (*chainreg.PartialChainControl, *btcwallet.Config, func(), error) { @@ -548,6 +601,8 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, HeightHintDB: dbs.HeightHintDB, ChanStateDB: dbs.ChanStateDB.ChannelStateDB(), NeutrinoCS: neutrinoCS, + AuxLeafStore: aux.AuxLeafStore, + AuxSigner: aux.AuxSigner, ActiveNetParams: d.cfg.ActiveNetParams, FeeURL: d.cfg.FeeURL, Fee: &lncfg.Fee{ @@ -611,8 +666,9 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, // proxyBlockEpoch proxies a block epoch subsections to the underlying neutrino // rebroadcaster client. -func proxyBlockEpoch(notifier chainntnfs.ChainNotifier, -) func() (*blockntfns.Subscription, error) { +func proxyBlockEpoch( + notifier chainntnfs.ChainNotifier) func() (*blockntfns.Subscription, + error) { return func() (*blockntfns.Subscription, error) { blockEpoch, err := notifier.RegisterBlockEpochNtfn( @@ -703,6 +759,8 @@ func (d *DefaultWalletImpl) BuildChainControl( ChainIO: walletController, NetParams: *walletConfig.NetParams, CoinSelectionStrategy: walletConfig.CoinSelectionStrategy, + AuxLeafStore: partialChainControl.Cfg.AuxLeafStore, + AuxSigner: partialChainControl.Cfg.AuxSigner, } // The broadcast is already always active for neutrino nodes, so we diff --git a/contractcourt/breach_arbitrator.go b/contractcourt/breach_arbitrator.go index e3dd85ea6f..a8154d0e61 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, nil, + ) + }) + 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, } } @@ -1125,6 +1160,11 @@ func (bo *breachedOutput) SignDesc() *input.SignDescriptor { return &bo.signDesc } +// Preimage returns the preimage that was used to create the breached output. +func (bo *breachedOutput) Preimage() fn.Option[lntypes.Preimage] { + return fn.None[lntypes.Preimage]() +} + // CraftInputScript computes a valid witness that allows us to spend from the // breached output. It does so by first generating and memoizing the witness // generation function, which parameterized primarily by the witness type and @@ -1174,6 +1214,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 +1304,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, nil, breachInfo.LocalOutputSignDesc, breachInfo.BreachHeight, + breachInfo.LocalResolutionBlob, ) breachedOutputs = append(breachedOutputs, localOutput) @@ -1284,6 +1331,7 @@ func newRetributionInfo(chanPoint *wire.OutPoint, nil, breachInfo.RemoteOutputSignDesc, breachInfo.BreachHeight, + breachInfo.RemoteResolutionBlob, ) breachedOutputs = append(breachedOutputs, remoteOutput) @@ -1318,6 +1366,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 +1406,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 +1473,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 +1492,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 +1531,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 +1576,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 +1601,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 +1620,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 +1693,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 +1729,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 +1765,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 +1788,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 +1815,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 +1824,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 6a1865444b..bd4ad85683 100644 --- a/contractcourt/breach_arbitrator_test.go +++ b/contractcourt/breach_arbitrator_test.go @@ -22,6 +22,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntest/channels" @@ -1198,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 @@ -1216,6 +1219,7 @@ func TestBreachCreateJusticeTx(t *testing.T) { nil, signDesc, 1, + rBlob, ) } @@ -1226,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. @@ -1590,6 +1594,10 @@ func testBreachSpends(t *testing.T, test breachTest) { // Notify the breach arbiter about the breach. 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") @@ -1799,6 +1807,10 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) { // Notify the breach arbiter about the breach. 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") @@ -2126,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 { @@ -2357,9 +2373,12 @@ func createInitChannels(t *testing.T) ( ) bobSigner := input.NewMockSigner([]*btcec.PrivateKey{bobKeyPriv}, nil) + signerMock := lnwallet.NewDefaultAuxSignerMock(t) alicePool := lnwallet.NewSigPool(1, aliceSigner) channelAlice, err := lnwallet.NewLightningChannel( aliceSigner, aliceChannelState, alicePool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, nil, err @@ -2372,6 +2391,8 @@ func createInitChannels(t *testing.T) ( bobPool := lnwallet.NewSigPool(1, bobSigner) channelBob, err := lnwallet.NewLightningChannel( bobSigner, bobChannelState, bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, nil, err diff --git a/contractcourt/briefcase.go b/contractcourt/briefcase.go index 95f6a933d7..65608e7708 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,9 +1555,16 @@ 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), + ) + }) } + htlcBlobs := newAuxHtlcBlobs() for _, htlc := range c.HtlcResolutions.IncomingHTLCs { htlc := htlc @@ -1566,12 +1575,13 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { continue } + var resID resolverID if htlc.SignedSuccessTx != nil { - resID := newResolverID( + resID = newResolverID( 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,13 +1590,17 @@ 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) + resID = newResolverID(htlc.ClaimOutpoint) //nolint:lll - tapCase.CtrlBlocks.IncomingHtlcCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.IncomingHtlcCtrlBlocks[resID] = ctrlBlock } + + htlc.ResolutionBlob.WhenSome(func(b []byte) { + htlcBlobs[resID] = b + }) } for _, htlc := range c.HtlcResolutions.OutgoingHTLCs { htlc := htlc @@ -1598,12 +1612,13 @@ func encodeTaprootAuxData(w io.Writer, c *ContractResolutions) error { continue } + var resID resolverID if htlc.SignedTimeoutTx != nil { - resID := newResolverID( + resID = newResolverID( 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 +1629,28 @@ 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) + resID = newResolverID(htlc.ClaimOutpoint) //nolint:lll - tapCase.CtrlBlocks.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock + tapCase.CtrlBlocks.Val.OutgoingHtlcCtrlBlocks[resID] = ctrlBlock } + + htlc.ResolutionBlob.WhenSome(func(b []byte) { + htlcBlobs[resID] = b + }) } if c.AnchorResolution != nil { anchorSignDesc := c.AnchorResolution.AnchorSignDescriptor - tapCase.TapTweaks.AnchorTweak = anchorSignDesc.TapTweak + tapCase.TapTweaks.Val.AnchorTweak = anchorSignDesc.TapTweak + } + + if len(htlcBlobs) != 0 { + tapCase.HtlcBlobs = tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType4](htlcBlobs), + ) } return tapCase.Encode(w) @@ -1639,9 +1664,15 @@ 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) + }) } + htlcBlobs := tapCase.HtlcBlobs.ValOpt().UnwrapOr(newAuxHtlcBlobs()) + for i := range c.HtlcResolutions.IncomingHTLCs { htlc := c.HtlcResolutions.IncomingHTLCs[i] @@ -1652,23 +1683,28 @@ 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 } + if htlcBlob, ok := htlcBlobs[resID]; ok { + htlc.ResolutionBlob = fn.Some(htlcBlob) + } + c.HtlcResolutions.IncomingHTLCs[i] = htlc + } for i := range c.HtlcResolutions.OutgoingHTLCs { htlc := c.HtlcResolutions.OutgoingHTLCs[i] @@ -1680,28 +1716,32 @@ 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 } + if htlcBlob, ok := htlcBlobs[resID]; ok { + htlc.ResolutionBlob = fn.Some(htlcBlob) + } + c.HtlcResolutions.OutgoingHTLCs[i] = htlc } 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 0cc4b111a5..d7d10ba252 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -217,6 +217,18 @@ type ChainArbitratorConfig struct { // meanwhile, turn `PaymentCircuit` into an interface or bring it to a // lower package. QueryIncomingCircuit func(circuit models.CircuitKey) *models.CircuitKey + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // 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 @@ -299,8 +311,19 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, return nil, err } + var chanOpts []lnwallet.ChannelOpt + a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + 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, + a.c.cfg.Signer, channel, nil, chanOpts..., ) if err != nil { return nil, err @@ -312,11 +335,10 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, // ForceCloseChan should force close the contract that this attendant is // watching over. We'll use this when we decide that we need to go to chain. It // should in addition tell the switch to remove the corresponding link, such -// that we won't accept any new updates. The returned summary contains all items -// needed to eventually resolve all outputs on chain. +// that we won't accept any new updates. // // NOTE: Part of the ArbChannel interface. -func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) { +func (a *arbChannel) ForceCloseChan() (*wire.MsgTx, error) { // First, we mark the channel as borked, this ensure // that no new state transitions can happen, and also // that the link won't be loaded into the switch. @@ -344,15 +366,34 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) return nil, err } + var chanOpts []lnwallet.ChannelOpt + a.c.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + 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. chanMachine, err := lnwallet.NewLightningChannel( - a.c.cfg.Signer, channel, nil, + a.c.cfg.Signer, channel, nil, chanOpts..., + ) + if err != nil { + return nil, err + } + + closeSummary, err := chanMachine.ForceClose( + lnwallet.WithSkipContractResolutions(), ) if err != nil { return nil, err } - return chanMachine.ForceClose() + + return closeSummary.CloseTx, nil } // newActiveChannelArbitrator creates a new instance of an active channel @@ -557,6 +598,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 { @@ -1186,6 +1229,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 3cbc7422de..48d092ae97 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -193,6 +193,12 @@ type chainWatcherConfig struct { // obfuscater. This is used by the chain watcher to identify which // state was broadcast and confirmed on-chain. extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64 + + // 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 @@ -308,7 +314,7 @@ func (c *chainWatcher) Start() error { ) if chanState.ChanType.IsTaproot() { c.fundingPkScript, _, err = input.GenTaprootFundingScript( - localKey, remoteKey, 0, + localKey, remoteKey, 0, chanState.TapscriptRoot, ) if err != nil { return err @@ -423,15 +429,37 @@ func (c *chainWatcher) handleUnknownLocalState( &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg, ) + auxResult, err := fn.MapOptionZ( + c.cfg.auxLeafStore, + //nolint:lll + func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + lnwallet.NewAuxChanState(c.cfg.chanState), + c.cfg.chanState.LocalCommitment, *commitKeyRing, + lntypes.Local, + ) + }, + ).Unpack() + if err != nil { + return false, fmt.Errorf("unable to fetch aux leaves: %w", err) + } + // With the keys derived, we'll construct the remote script that'll be // present if they have a non-dust balance on the commitment. var leaseExpiry uint32 if c.cfg.chanState.ChanType.HasLeaseExpiration() { leaseExpiry = c.cfg.chanState.ThawHeight } + + remoteAuxLeaf := fn.ChainOption( + func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf { + return l.RemoteAuxLeaf + }, + )(auxResult.AuxLeaves) remoteScript, _, err := lnwallet.CommitScriptToRemote( c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, commitKeyRing.ToRemoteKey, leaseExpiry, + remoteAuxLeaf, ) if err != nil { return false, err @@ -440,10 +468,16 @@ func (c *chainWatcher) handleUnknownLocalState( // Next, we'll derive our script that includes the revocation base for // the remote party allowing them to claim this output before the CSV // delay if we breach. + localAuxLeaf := fn.ChainOption( + func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf { + return l.LocalAuxLeaf + }, + )(auxResult.AuxLeaves) localScript, err := lnwallet.CommitScriptToSelf( c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey, uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry, + localAuxLeaf, ) if err != nil { return false, err @@ -866,7 +900,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, spendHeight := uint32(commitSpend.SpendingHeight) retribution, err := lnwallet.NewBreachRetribution( c.cfg.chanState, broadcastStateNum, spendHeight, - commitSpend.SpendingTx, + commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver, ) switch { @@ -1116,8 +1150,8 @@ func (c *chainWatcher) dispatchLocalForceClose( "detected", c.cfg.chanState.FundingOutpoint) forceClose, err := lnwallet.NewLocalForceCloseSummary( - c.cfg.chanState, c.cfg.signer, - commitSpend.SpendingTx, stateNum, + c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum, + c.cfg.auxLeafStore, c.cfg.auxResolver, ) if err != nil { return err @@ -1141,16 +1175,29 @@ func (c *chainWatcher) dispatchLocalForceClose( LocalChanConfig: c.cfg.chanState.LocalChanCfg, } + resolutions, err := forceClose.ContractResolutions.UnwrapOrErr( + fmt.Errorf("resolutions not found"), + ) + if err != nil { + return err + } + // If our commitment output isn't dust or we have active HTLC's on the // commitment transaction, then we'll populate the balances on the // close channel summary. - if forceClose.CommitResolution != nil { - closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis() - closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis() + if resolutions.CommitResolution != nil { + localBalance := chanSnapshot.LocalBalance.ToSatoshis() + closeSummary.SettledBalance = localBalance + closeSummary.TimeLockedBalance = localBalance } - for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs { - htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value) - closeSummary.TimeLockedBalance += htlcValue + + if resolutions.HtlcResolutions != nil { + for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs { + htlcValue := btcutil.Amount( + htlc.SweepSignDesc.Output.Value, + ) + closeSummary.TimeLockedBalance += htlcValue + } } // Attempt to add a channel sync message to the close summary. @@ -1209,8 +1256,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.chanState, c.cfg.signer, commitSpend, remoteCommit, + commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver, ) if err != nil { return err diff --git a/contractcourt/chain_watcher_test.go b/contractcourt/chain_watcher_test.go index 489a605185..baea8d8738 100644 --- a/contractcourt/chain_watcher_test.go +++ b/contractcourt/chain_watcher_test.go @@ -2,6 +2,7 @@ package contractcourt import ( "bytes" + "context" "crypto/sha256" "fmt" "testing" @@ -145,17 +146,15 @@ func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) { // With the HTLC added, we'll now manually initiate a state transition // from Alice to Bob. - _, err = aliceChannel.SignNextCommitment() - if err != nil { - t.Fatal(err) - } + testQuit, testQuitFunc := context.WithCancel(context.Background()) + t.Cleanup(testQuitFunc) + _, err = aliceChannel.SignNextCommitment(testQuit) + require.NoError(t, err) // At this point, we'll now Bob broadcasting this new pending unrevoked // commitment. bobPendingCommit, err := aliceChannel.State().RemoteCommitChainTip() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // We'll craft a fake spend notification with Bob's actual commitment. // The chain watcher should be able to detect that this is a pending @@ -505,14 +504,24 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) { // outputs. select { case summary := <-chanEvents.LocalUnilateralClosure: + resOpt := summary.LocalForceCloseSummary. + ContractResolutions + + resolutions, err := resOpt.UnwrapOrErr( + fmt.Errorf("resolutions not found"), + ) + if err != nil { + t.Fatalf("unable to get resolutions: %v", err) + } + // Make sure we correctly extracted the commit // resolution if we had a local output. if remoteOutputOnly { - if summary.CommitResolution != nil { + if resolutions.CommitResolution != nil { t.Fatalf("expected no commit resolution") } } else { - if summary.CommitResolution == nil { + if resolutions.CommitResolution == nil { t.Fatalf("expected commit resolution") } } diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index cb5cee8720..46af3e5aeb 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -98,7 +98,7 @@ type ArbChannel interface { // corresponding link, such that we won't accept any new updates. The // returned summary contains all items needed to eventually resolve all // outputs on chain. - ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) + ForceCloseChan() (*wire.MsgTx, error) // NewAnchorResolutions returns the anchor resolutions for currently // valid commitment transactions. @@ -1058,7 +1058,7 @@ func (c *ChannelArbitrator) stateStep( // We'll tell the switch that it should remove the link for // this channel, in addition to fetching the force close // summary needed to close this channel on chain. - closeSummary, err := c.cfg.Channel.ForceCloseChan() + forceCloseTx, err := c.cfg.Channel.ForceCloseChan() if err != nil { log.Errorf("ChannelArbitrator(%v): unable to "+ "force close: %v", c.cfg.ChanPoint, err) @@ -1078,7 +1078,7 @@ func (c *ChannelArbitrator) stateStep( return StateError, closeTx, err } - closeTx = closeSummary.CloseTx + closeTx = forceCloseTx // Before publishing the transaction, we store it to the // database, such that we can re-publish later in case it @@ -1982,9 +1982,11 @@ func (c *ChannelArbitrator) isPreimageAvailable(hash lntypes.Hash) (bool, // have the incoming contest resolver decide that we don't want to // settle this invoice. invoice, err := c.cfg.Registry.LookupInvoice(context.Background(), hash) - switch err { - case nil: - case invoices.ErrInvoiceNotFound, invoices.ErrNoInvoicesCreated: + switch { + case err == nil: + case errors.Is(err, invoices.ErrInvoiceNotFound) || + errors.Is(err, invoices.ErrNoInvoicesCreated): + return false, nil default: return false, err @@ -2869,11 +2871,36 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { } closeTx := closeInfo.CloseTx + resolutions, err := closeInfo.ContractResolutions. + UnwrapOrErr( + fmt.Errorf("resolutions not found"), + ) + if err != nil { + log.Errorf("ChannelArbitrator(%v): unable to "+ + "get resolutions: %v", c.cfg.ChanPoint, + err) + + return + } + + // We make sure that the htlc resolutions are present + // otherwise we would panic dereferencing the pointer. + // + // TODO(ziggie): Refactor ContractResolutions to use + // options. + if resolutions.HtlcResolutions == nil { + log.Errorf("ChannelArbitrator(%v): htlc "+ + "resolutions not found", + c.cfg.ChanPoint) + + return + } + contractRes := &ContractResolutions{ CommitHash: closeTx.TxHash(), - CommitResolution: closeInfo.CommitResolution, - HtlcResolutions: *closeInfo.HtlcResolutions, - AnchorResolution: closeInfo.AnchorResolution, + CommitResolution: resolutions.CommitResolution, + HtlcResolutions: *resolutions.HtlcResolutions, + AnchorResolution: resolutions.AnchorResolution, } // When processing a unilateral close event, we'll @@ -2882,7 +2909,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // available to fetch in that state, we'll also write // the commit set so we can reconstruct our chain // actions on restart. - err := c.log.LogContractResolutions(contractRes) + err = c.log.LogContractResolutions(contractRes) if err != nil { log.Errorf("Unable to write resolutions: %v", err) diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 916cd5f580..7f8c4b087f 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -693,11 +693,15 @@ func TestChannelArbitratorLocalForceClose(t *testing.T) { chanArbCtx.AssertState(StateCommitmentBroadcasted) // Now notify about the local force close getting confirmed. + // + //nolint:lll chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ SpendDetail: &chainntnfs.SpendDetail{}, LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ - CloseTx: &wire.MsgTx{}, - HtlcResolutions: &lnwallet.HtlcResolutions{}, + CloseTx: &wire.MsgTx{}, + ContractResolutions: fn.Some(lnwallet.ContractResolutions{ + HtlcResolutions: &lnwallet.HtlcResolutions{}, + }), }, ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, } @@ -969,15 +973,18 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { }, } + //nolint:lll chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ SpendDetail: &chainntnfs.SpendDetail{}, LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ CloseTx: closeTx, - HtlcResolutions: &lnwallet.HtlcResolutions{ - OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{ - outgoingRes, + ContractResolutions: fn.Some(lnwallet.ContractResolutions{ + HtlcResolutions: &lnwallet.HtlcResolutions{ + OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{ + outgoingRes, + }, }, - }, + }), }, ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, CommitSet: CommitSet{ @@ -1611,12 +1618,15 @@ func TestChannelArbitratorCommitFailure(t *testing.T) { }, { closeType: channeldb.LocalForceClose, + //nolint:lll sendEvent: func(chanArb *ChannelArbitrator) { chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ SpendDetail: &chainntnfs.SpendDetail{}, LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ - CloseTx: &wire.MsgTx{}, - HtlcResolutions: &lnwallet.HtlcResolutions{}, + CloseTx: &wire.MsgTx{}, + ContractResolutions: fn.Some(lnwallet.ContractResolutions{ + HtlcResolutions: &lnwallet.HtlcResolutions{}, + }), }, ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, } @@ -1944,11 +1954,15 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) { // being canalled back. Also note that there're no HTLC // resolutions sent since we have none on our // commitment transaction. + // + //nolint:lll uniCloseInfo := &LocalUnilateralCloseInfo{ SpendDetail: &chainntnfs.SpendDetail{}, LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ - CloseTx: closeTx, - HtlcResolutions: &lnwallet.HtlcResolutions{}, + CloseTx: closeTx, + ContractResolutions: fn.Some(lnwallet.ContractResolutions{ + HtlcResolutions: &lnwallet.HtlcResolutions{}, + }), }, ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, CommitSet: CommitSet{ @@ -2754,12 +2768,15 @@ func TestChannelArbitratorAnchors(t *testing.T) { }, } + //nolint:lll chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ SpendDetail: &chainntnfs.SpendDetail{}, LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ - CloseTx: closeTx, - HtlcResolutions: &lnwallet.HtlcResolutions{}, - AnchorResolution: anchorResolution, + CloseTx: closeTx, + ContractResolutions: fn.Some(lnwallet.ContractResolutions{ + HtlcResolutions: &lnwallet.HtlcResolutions{}, + AnchorResolution: anchorResolution, + }), }, ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, CommitSet: CommitSet{ @@ -2993,14 +3010,10 @@ func (m *mockChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions, return &lnwallet.AnchorResolutions{}, nil } -func (m *mockChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) { +func (m *mockChannel) ForceCloseChan() (*wire.MsgTx, error) { if m.forceCloseErr != nil { return nil, m.forceCloseErr } - summary := &lnwallet.LocalForceCloseSummary{ - CloseTx: &wire.MsgTx{}, - HtlcResolutions: &lnwallet.HtlcResolutions{}, - } - return summary, nil + return &wire.MsgTx{}, nil } diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index f2392192e8..8f11695801 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 b104f8d70a..6bda4e398b 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -308,7 +308,7 @@ func (h *htlcIncomingContestResolver) Resolve( resolution, err := h.Registry.NotifyExitHopHtlc( h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight, - circuitKey, hodlQueue.ChanIn(), payload, + circuitKey, hodlQueue.ChanIn(), nil, payload, ) if err != nil { return nil, err diff --git a/contractcourt/htlc_lease_resolver.go b/contractcourt/htlc_lease_resolver.go index 87e55d5cc4..53fa893553 100644 --- a/contractcourt/htlc_lease_resolver.go +++ b/contractcourt/htlc_lease_resolver.go @@ -6,7 +6,9 @@ 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/tlv" ) // htlcLeaseResolver is a struct that houses the lease specific HTLC resolution @@ -52,8 +54,8 @@ func (h *htlcLeaseResolver) deriveWaitHeight(csvDelay uint32, // send to the sweeper so the output can ultimately be swept. func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint, wType, cltvWtype input.StandardWitnessType, - signDesc *input.SignDescriptor, - csvDelay, broadcastHeight uint32, payHash [32]byte) *input.BaseInput { + signDesc *input.SignDescriptor, csvDelay, broadcastHeight uint32, + payHash [32]byte, resBlob fn.Option[tlv.Blob]) *input.BaseInput { if h.hasCLTV() { log.Infof("%T(%x): CSV and CLTV locks expired, offering "+ @@ -63,13 +65,17 @@ func (h *htlcLeaseResolver) makeSweepInput(op *wire.OutPoint, op, cltvWtype, signDesc, broadcastHeight, csvDelay, h.leaseExpiry, + input.WithResolutionBlob(resBlob), ) } log.Infof("%T(%x): CSV lock expired, offering second-layer output to "+ "sweeper: %v", h, payHash, op) - return input.NewCsvInput(op, wType, signDesc, broadcastHeight, csvDelay) + return input.NewCsvInput( + op, wType, signDesc, broadcastHeight, csvDelay, + input.WithResolutionBlob(resBlob), + ) } // SupplementState allows the user of a ContractResolver to supplement it with diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 6eee939eac..4c9d2b200b 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -247,6 +247,9 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) ( h.htlcResolution.SignedSuccessTx, h.htlcResolution.SignDetails, h.htlcResolution.Preimage, h.broadcastHeight, + input.WithResolutionBlob( + h.htlcResolution.ResolutionBlob, + ), ) } else { //nolint:lll @@ -403,7 +406,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) ( input.LeaseHtlcAcceptedSuccessSecondLevel, &h.htlcResolution.SweepSignDesc, h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight), - h.htlc.RHash, + h.htlc.RHash, h.htlcResolution.ResolutionBlob, ) // Calculate the budget for this sweep. @@ -459,6 +462,9 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) ( h.htlcResolution.Preimage[:], h.broadcastHeight, h.htlcResolution.CsvDelay, + input.WithResolutionBlob( + h.htlcResolution.ResolutionBlob, + ), )) } else { inp = lnutils.Ptr(input.MakeHtlcSucceedInput( diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 62ff832071..f2de5f4a37 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -484,6 +484,9 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error { h.htlcResolution.SignedTimeoutTx, h.htlcResolution.SignDetails, h.broadcastHeight, + input.WithResolutionBlob( + h.htlcResolution.ResolutionBlob, + ), )) } else { inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutAnchorInput( @@ -538,7 +541,6 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error { return err } - // TODO(yy): checkpoint here? return err } @@ -562,6 +564,60 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error { return h.Checkpoint(h) } +// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the +// sweeper. This is used when the remote party goes on chain, and we're able to +// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs +// are resolved via this path. +func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error { + var htlcWitnessType input.StandardWitnessType + if h.isTaproot() { + htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout + } else { + htlcWitnessType = input.HtlcOfferedRemoteTimeout + } + + sweepInput := input.NewCsvInputWithCltv( + &h.htlcResolution.ClaimOutpoint, htlcWitnessType, + &h.htlcResolution.SweepSignDesc, h.broadcastHeight, + h.htlcResolution.CsvDelay, h.htlcResolution.Expiry, + input.WithResolutionBlob(h.htlcResolution.ResolutionBlob), + ) + + // Calculate the budget. + // + // TODO(yy): the budget is twice the output's value, which is needed as + // we don't force sweep the output now. To prevent cascading force + // closes, we use all its output value plus a wallet input as the + // budget. This is a temporary solution until we can optionally cancel + // the incoming HTLC, more details in, + // - https://github.com/lightningnetwork/lnd/issues/7969 + budget := calculateBudget( + btcutil.Amount(sweepInput.SignDesc().Output.Value), 2, 0, + ) + + log.Infof("%T(%x): offering offered remote timeout HTLC output to "+ + "sweeper with deadline %v and budget=%v at height=%v", + h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget, + h.broadcastHeight) + + _, err := h.Sweeper.SweepInput( + sweepInput, + sweep.Params{ + Budget: budget, + + // This is an outgoing HTLC, so we want to make sure + // that we sweep it before the incoming HTLC expires. + DeadlineHeight: h.incomingHTLCExpiryHeight, + Immediate: immediate, + }, + ) + if err != nil { + return err + } + + return nil +} + // spendHtlcOutput handles the initial spend of an HTLC output via the timeout // clause. If this is our local commitment, the second-level timeout TX will be // used to spend the output into the next stage. If this is the remote @@ -582,8 +638,18 @@ func (h *htlcTimeoutResolver) spendHtlcOutput( return nil, err } - // If we have no SignDetails, and we haven't already sent the output to - // the utxo nursery, then we'll do so now. + // If this is a remote commitment there's no second level timeout txn, + // and we can just send this directly to the sweeper. + case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating: + if err := h.sweepDirectHtlcOutput(immediate); err != nil { + log.Errorf("Sending direct spend to sweeper: %v", err) + + return nil, err + } + + // If we have a SignedTimeoutTx but no SignDetails, this is a local + // commitment for a non-anchor channel, so we'll send it to the utxo + // nursery. case h.htlcResolution.SignDetails == nil && !h.outputIncubating: if err := h.sendSecondLevelTxLegacy(); err != nil { log.Errorf("Sending timeout tx to nursery: %v", err) @@ -690,6 +756,13 @@ func (h *htlcTimeoutResolver) handleCommitSpend( ) switch { + + // If we swept an HTLC directly off the remote party's commitment + // transaction, then we can exit here as there's no second level sweep + // to do. + case h.htlcResolution.SignedTimeoutTx == nil: + break + // If the sweeper is handling the second level transaction, wait for // the CSV and possible CLTV lock to expire, before sweeping the output // on the second-level. @@ -762,7 +835,9 @@ func (h *htlcTimeoutResolver) handleCommitSpend( &h.htlcResolution.SweepSignDesc, h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight), h.htlc.RHash, + h.htlcResolution.ResolutionBlob, ) + // Calculate the budget for this sweep. budget := calculateBudget( btcutil.Amount(inp.SignDesc().Output.Value), @@ -800,6 +875,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend( case h.htlcResolution.SignedTimeoutTx != nil: log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+ "delayed output", h, claimOutpoint) + sweepTx, err := waitForSpend( &claimOutpoint, h.htlcResolution.SweepSignDesc.Output.PkScript, @@ -866,9 +942,11 @@ func (h *htlcTimeoutResolver) IsResolved() bool { // report returns a report on the resolution state of the contract. func (h *htlcTimeoutResolver) report() *ContractReport { - // If the sign details are nil, the report will be created by handled - // by the nursery. - if h.htlcResolution.SignDetails == nil { + // If we have a SignedTimeoutTx but no SignDetails, this is a local + // commitment for a non-anchor channel, which was handled by the utxo + // nursery. + if h.htlcResolution.SignDetails == nil && h. + htlcResolution.SignedTimeoutTx != nil { return nil } @@ -888,13 +966,20 @@ func (h *htlcTimeoutResolver) initReport() { ) } + // If there's no timeout transaction, then we're already effectively in + // level two. + stage := uint32(1) + if h.htlcResolution.SignedTimeoutTx == nil { + stage = 2 + } + h.currentReport = ContractReport{ Outpoint: h.htlcResolution.ClaimOutpoint, Type: ReportOutputOutgoingHtlc, Amount: finalAmt, MaturityHeight: h.htlcResolution.Expiry, LimboBalance: finalAmt, - Stage: 1, + Stage: stage, } } diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index c551a6f1ce..47be71d3ec 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -69,11 +69,31 @@ func (m *mockWitnessBeacon) AddPreimages(preimages ...lntypes.Preimage) error { return nil } -// TestHtlcTimeoutResolver tests that the timeout resolver properly handles all -// variations of possible local+remote spends. -func TestHtlcTimeoutResolver(t *testing.T) { - t.Parallel() +type htlcTimeoutTestCase struct { + // name is a human readable description of the test case. + name string + + // remoteCommit denotes if the commitment broadcast was the remote + // commitment or not. + remoteCommit bool + + // timeout denotes if the HTLC should be let timeout, or if the "remote" + // party should sweep it on-chain. This also affects what type of + // resolution message we expect. + timeout bool + + // txToBroadcast is a function closure that should generate the + // transaction that should spend the HTLC output. Test authors can use + // this to customize the witness used when spending to trigger various + // redemption cases. + txToBroadcast func() (*wire.MsgTx, error) + + // outcome is the resolver outcome that we expect to be reported once + // the contract is fully resolved. + outcome channeldb.ResolverOutcome +} +func genHtlcTimeoutTestCases() []htlcTimeoutTestCase { fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize) var ( @@ -105,29 +125,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { }, } - testCases := []struct { - // name is a human readable description of the test case. - name string - - // remoteCommit denotes if the commitment broadcast was the - // remote commitment or not. - remoteCommit bool - - // timeout denotes if the HTLC should be let timeout, or if the - // "remote" party should sweep it on-chain. This also affects - // what type of resolution message we expect. - timeout bool - - // txToBroadcast is a function closure that should generate the - // transaction that should spend the HTLC output. Test authors - // can use this to customize the witness used when spending to - // trigger various redemption cases. - txToBroadcast func() (*wire.MsgTx, error) - - // outcome is the resolver outcome that we expect to be reported - // once the contract is fully resolved. - outcome channeldb.ResolverOutcome - }{ + return []htlcTimeoutTestCase{ // Remote commitment is broadcast, we time out the HTLC on // chain, and should expect a fail HTLC resolution. { @@ -149,7 +147,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -219,7 +218,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -253,7 +253,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { // immediately if the witness is already set // correctly. if reflect.DeepEqual( - templateTx.TxIn[0].Witness, witness, + templateTx.TxIn[0].Witness, + witness, ) { return templateTx, nil @@ -265,243 +266,280 @@ func TestHtlcTimeoutResolver(t *testing.T) { outcome: channeldb.ResolverOutcomeClaimed, }, } +} + +func testHtlcTimeoutResolver(t *testing.T, testCase htlcTimeoutTestCase) { + fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize) + var fakePreimage lntypes.Preimage + + fakeSignDesc := &input.SignDescriptor{ + Output: &wire.TxOut{}, + } + + copy(fakePreimage[:], fakePreimageBytes) notifier := &mock.ChainNotifier{ EpochChan: make(chan *chainntnfs.BlockEpoch), SpendChan: make(chan *chainntnfs.SpendDetail), ConfChan: make(chan *chainntnfs.TxConfirmation), } + witnessBeacon := newMockWitnessBeacon() + checkPointChan := make(chan struct{}, 1) + incubateChan := make(chan struct{}, 1) + resolutionChan := make(chan ResolutionMsg, 1) + reportChan := make(chan *channeldb.ResolverReport) + + //nolint:lll + chainCfg := ChannelArbitratorConfig{ + ChainArbitratorConfig: ChainArbitratorConfig{ + Notifier: notifier, + Sweeper: newMockSweeper(), + PreimageDB: witnessBeacon, + IncubateOutputs: func(wire.OutPoint, + fn.Option[lnwallet.OutgoingHtlcResolution], + fn.Option[lnwallet.IncomingHtlcResolution], + uint32, fn.Option[int32]) error { + + incubateChan <- struct{}{} + return nil + }, + DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { + if len(msgs) != 1 { + return fmt.Errorf("expected 1 "+ + "resolution msg, instead got %v", + len(msgs)) + } - for _, testCase := range testCases { - t.Logf("Running test case: %v", testCase.name) - - checkPointChan := make(chan struct{}, 1) - incubateChan := make(chan struct{}, 1) - resolutionChan := make(chan ResolutionMsg, 1) - reportChan := make(chan *channeldb.ResolverReport) - - //nolint:lll - chainCfg := ChannelArbitratorConfig{ - ChainArbitratorConfig: ChainArbitratorConfig{ - Notifier: notifier, - PreimageDB: witnessBeacon, - IncubateOutputs: func(wire.OutPoint, - fn.Option[lnwallet.OutgoingHtlcResolution], - fn.Option[lnwallet.IncomingHtlcResolution], - uint32, fn.Option[int32]) error { - - incubateChan <- struct{}{} - return nil - }, - DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { - if len(msgs) != 1 { - return fmt.Errorf("expected 1 "+ - "resolution msg, instead got %v", - len(msgs)) - } + resolutionChan <- msgs[0] - resolutionChan <- msgs[0] - return nil - }, - Budget: *DefaultBudgetConfig(), - QueryIncomingCircuit: func(circuit models.CircuitKey) *models.CircuitKey { - return nil - }, + return nil }, - PutResolverReport: func(_ kvdb.RwTx, - _ *channeldb.ResolverReport) error { + Budget: *DefaultBudgetConfig(), + QueryIncomingCircuit: func(circuit models.CircuitKey, + ) *models.CircuitKey { return nil }, - } + }, + PutResolverReport: func(_ kvdb.RwTx, + _ *channeldb.ResolverReport) error { - cfg := ResolverConfig{ - ChannelArbitratorConfig: chainCfg, - Checkpoint: func(_ ContractResolver, - reports ...*channeldb.ResolverReport) error { + return nil + }, + } - checkPointChan <- struct{}{} + cfg := ResolverConfig{ + ChannelArbitratorConfig: chainCfg, + Checkpoint: func(_ ContractResolver, + reports ...*channeldb.ResolverReport) error { - // Send all of our reports into the channel. - for _, report := range reports { - reportChan <- report - } + checkPointChan <- struct{}{} - return nil - }, - } - resolver := &htlcTimeoutResolver{ - htlcResolution: lnwallet.OutgoingHtlcResolution{ - ClaimOutpoint: testChanPoint2, - SweepSignDesc: *fakeSignDesc, - }, - contractResolverKit: *newContractResolverKit( - cfg, - ), - htlc: channeldb.HTLC{ - Amt: testHtlcAmt, - }, - } + // Send all of our reports into the channel. + for _, report := range reports { + reportChan <- report + } - var reports []*channeldb.ResolverReport + return nil + }, + } + resolver := &htlcTimeoutResolver{ + htlcResolution: lnwallet.OutgoingHtlcResolution{ + ClaimOutpoint: testChanPoint2, + SweepSignDesc: *fakeSignDesc, + }, + contractResolverKit: *newContractResolverKit( + cfg, + ), + htlc: channeldb.HTLC{ + Amt: testHtlcAmt, + }, + } - // If the test case needs the remote commitment to be - // broadcast, then we'll set the timeout commit to a fake - // transaction to force the code path. - if !testCase.remoteCommit { - timeoutTx, err := testCase.txToBroadcast() - require.NoError(t, err) - - resolver.htlcResolution.SignedTimeoutTx = timeoutTx - - if testCase.timeout { - timeoutTxID := timeoutTx.TxHash() - reports = append(reports, &channeldb.ResolverReport{ - OutPoint: timeoutTx.TxIn[0].PreviousOutPoint, - Amount: testHtlcAmt.ToSatoshis(), - ResolverType: channeldb.ResolverTypeOutgoingHtlc, - ResolverOutcome: channeldb.ResolverOutcomeFirstStage, - SpendTxID: &timeoutTxID, - }) + var reports []*channeldb.ResolverReport + + // If the test case needs the remote commitment to be + // broadcast, then we'll set the timeout commit to a fake + // transaction to force the code path. + if !testCase.remoteCommit { + timeoutTx, err := testCase.txToBroadcast() + require.NoError(t, err) + + resolver.htlcResolution.SignedTimeoutTx = timeoutTx + + if testCase.timeout { + timeoutTxID := timeoutTx.TxHash() + report := &channeldb.ResolverReport{ + OutPoint: timeoutTx.TxIn[0].PreviousOutPoint, //nolint:lll + Amount: testHtlcAmt.ToSatoshis(), + ResolverType: channeldb.ResolverTypeOutgoingHtlc, //nolint:lll + ResolverOutcome: channeldb.ResolverOutcomeFirstStage, //nolint:lll + SpendTxID: &timeoutTxID, } + + reports = append(reports, report) } + } - // With all the setup above complete, we can initiate the - // resolution process, and the bulk of our test. - var wg sync.WaitGroup - resolveErr := make(chan error, 1) - wg.Add(1) - go func() { - defer wg.Done() - - _, err := resolver.Resolve(false) - if err != nil { - resolveErr <- err - } - }() + // With all the setup above complete, we can initiate the + // resolution process, and the bulk of our test. + var wg sync.WaitGroup + resolveErr := make(chan error, 1) + wg.Add(1) + go func() { + defer wg.Done() + + _, err := resolver.Resolve(false) + if err != nil { + resolveErr <- err + } + }() + + // If this is a remote commit, then we expct the outputs should receive + // an incubation request to go through the sweeper, otherwise the + // nursery. + var sweepChan chan input.Input + if testCase.remoteCommit { + mockSweeper, ok := resolver.Sweeper.(*mockSweeper) + require.True(t, ok) + sweepChan = mockSweeper.sweptInputs + } + + // The output should be offered to either the sweeper or + // the nursery. + select { + case <-incubateChan: + case <-sweepChan: + case err := <-resolveErr: + t.Fatalf("unable to resolve HTLC: %v", err) + case <-time.After(time.Second * 5): + t.Fatalf("failed to receive incubation request") + } + + // Next, the resolver should request a spend notification for + // the direct HTLC output. We'll use the txToBroadcast closure + // for the test case to generate the transaction that we'll + // send to the resolver. + spendingTx, err := testCase.txToBroadcast() + if err != nil { + t.Fatalf("unable to generate tx: %v", err) + } + spendTxHash := spendingTx.TxHash() + + select { + case notifier.SpendChan <- &chainntnfs.SpendDetail{ + SpendingTx: spendingTx, + SpenderTxHash: &spendTxHash, + }: + case <-time.After(time.Second * 5): + t.Fatalf("failed to request spend ntfn") + } - // At the output isn't yet in the nursery, we expect that we - // should receive an incubation request. + if !testCase.timeout { + // If the resolver should settle now, then we'll + // extract the pre-image to be extracted and the + // resolution message sent. select { - case <-incubateChan: - case err := <-resolveErr: - t.Fatalf("unable to resolve HTLC: %v", err) + case newPreimage := <-witnessBeacon.newPreimages: + if newPreimage[0] != fakePreimage { + t.Fatalf("wrong pre-image: "+ + "expected %v, got %v", + fakePreimage, newPreimage) + } + case <-time.After(time.Second * 5): - t.Fatalf("failed to receive incubation request") + t.Fatalf("pre-image not added") } - // Next, the resolver should request a spend notification for - // the direct HTLC output. We'll use the txToBroadcast closure - // for the test case to generate the transaction that we'll - // send to the resolver. - spendingTx, err := testCase.txToBroadcast() - if err != nil { - t.Fatalf("unable to generate tx: %v", err) + // Finally, we should get a resolution message with the + // pre-image set within the message. + select { + case resolutionMsg := <-resolutionChan: + // Once again, the pre-images should match up. + if *resolutionMsg.PreImage != fakePreimage { + t.Fatalf("wrong pre-image: "+ + "expected %v, got %v", + fakePreimage, resolutionMsg.PreImage) + } + case <-time.After(time.Second * 5): + t.Fatalf("resolution not sent") } - spendTxHash := spendingTx.TxHash() - + } else { + // Otherwise, the HTLC should now timeout. First, we + // should get a resolution message with a populated + // failure message. select { - case notifier.SpendChan <- &chainntnfs.SpendDetail{ - SpendingTx: spendingTx, - SpenderTxHash: &spendTxHash, - }: + case resolutionMsg := <-resolutionChan: + if resolutionMsg.Failure == nil { + t.Fatalf("expected failure resolution msg") + } case <-time.After(time.Second * 5): - t.Fatalf("failed to request spend ntfn") + t.Fatalf("resolution not sent") } - if !testCase.timeout { - // If the resolver should settle now, then we'll - // extract the pre-image to be extracted and the - // resolution message sent. + // We should also get another request for the spend + // notification of the second-level transaction to + // indicate that it's been swept by the nursery, but + // only if this is a local commitment transaction. + if !testCase.remoteCommit { select { - case newPreimage := <-witnessBeacon.newPreimages: - if newPreimage[0] != fakePreimage { - t.Fatalf("wrong pre-image: "+ - "expected %v, got %v", - fakePreimage, newPreimage) - } - + case notifier.SpendChan <- &chainntnfs.SpendDetail{ + SpendingTx: spendingTx, + SpenderTxHash: &spendTxHash, + }: case <-time.After(time.Second * 5): - t.Fatalf("pre-image not added") + t.Fatalf("failed to request spend ntfn") } + } + } - // Finally, we should get a resolution message with the - // pre-image set within the message. - select { - case resolutionMsg := <-resolutionChan: - // Once again, the pre-images should match up. - if *resolutionMsg.PreImage != fakePreimage { - t.Fatalf("wrong pre-image: "+ - "expected %v, got %v", - fakePreimage, resolutionMsg.PreImage) - } - case <-time.After(time.Second * 5): - t.Fatalf("resolution not sent") - } - } else { + // In any case, before the resolver exits, it should checkpoint + // its final state. + select { + case <-checkPointChan: + case err := <-resolveErr: + t.Fatalf("unable to resolve HTLC: %v", err) + case <-time.After(time.Second * 5): + t.Fatalf("check point not received") + } - // Otherwise, the HTLC should now timeout. First, we - // should get a resolution message with a populated - // failure message. - select { - case resolutionMsg := <-resolutionChan: - if resolutionMsg.Failure == nil { - t.Fatalf("expected failure resolution msg") - } - case <-time.After(time.Second * 5): - t.Fatalf("resolution not sent") - } + // Add a report to our set of expected reports with the outcome + // that the test specifies (either success or timeout). + spendTxID := spendingTx.TxHash() + amt := btcutil.Amount(fakeSignDesc.Output.Value) - // We should also get another request for the spend - // notification of the second-level transaction to - // indicate that it's been swept by the nursery, but - // only if this is a local commitment transaction. - if !testCase.remoteCommit { - select { - case notifier.SpendChan <- &chainntnfs.SpendDetail{ - SpendingTx: spendingTx, - SpenderTxHash: &spendTxHash, - }: - case <-time.After(time.Second * 5): - t.Fatalf("failed to request spend ntfn") - } - } - } + reports = append(reports, &channeldb.ResolverReport{ + OutPoint: testChanPoint2, + Amount: amt, + ResolverType: channeldb.ResolverTypeOutgoingHtlc, + ResolverOutcome: testCase.outcome, + SpendTxID: &spendTxID, + }) - // In any case, before the resolver exits, it should checkpoint - // its final state. - select { - case <-checkPointChan: - case err := <-resolveErr: - t.Fatalf("unable to resolve HTLC: %v", err) - case <-time.After(time.Second * 5): - t.Fatalf("check point not received") - } + for _, report := range reports { + assertResolverReport(t, reportChan, report) + } - // Add a report to our set of expected reports with the outcome - // that the test specifies (either success or timeout). - spendTxID := spendingTx.TxHash() - amt := btcutil.Amount(fakeSignDesc.Output.Value) - - reports = append(reports, &channeldb.ResolverReport{ - OutPoint: testChanPoint2, - Amount: amt, - ResolverType: channeldb.ResolverTypeOutgoingHtlc, - ResolverOutcome: testCase.outcome, - SpendTxID: &spendTxID, - }) + wg.Wait() - for _, report := range reports { - assertResolverReport(t, reportChan, report) - } + // Finally, the resolver should be marked as resolved. + if !resolver.resolved { + t.Fatalf("resolver should be marked as resolved") + } +} - wg.Wait() +// TestHtlcTimeoutResolver tests that the timeout resolver properly handles all +// variations of possible local+remote spends. +func TestHtlcTimeoutResolver(t *testing.T) { + t.Parallel() - // Finally, the resolver should be marked as resolved. - if !resolver.resolved { - t.Fatalf("resolver should be marked as resolved") - } + testCases := genHtlcTimeoutTestCases() + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + testHtlcTimeoutResolver(t, testCase) + }) } } @@ -536,15 +574,12 @@ func TestHtlcTimeoutSingleStage(t *testing.T) { } checkpoints := []checkpoint{ - { - // Output should be handed off to the nursery. - incubating: true, - }, { // We send a confirmation the sweep tx from published // by the nursery. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + // The nursery will create and publish a sweep // tx. ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ @@ -570,7 +605,7 @@ func TestHtlcTimeoutSingleStage(t *testing.T) { // After the sweep has confirmed, we expect the // checkpoint to be resolved, and with the above // report. - incubating: true, + incubating: false, resolved: true, reports: []*channeldb.ResolverReport{ claim, @@ -653,6 +688,7 @@ func TestHtlcTimeoutSecondStage(t *testing.T) { // that our sweep succeeded. preCheckpoint: func(ctx *htlcResolverTestContext, _ bool) error { + // The nursery will publish the timeout tx. ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ SpendingTx: timeoutTx, @@ -824,9 +860,9 @@ func TestHtlcTimeoutSingleStageRemoteSpend(t *testing.T) { ) } -// TestHtlcTimeoutSecondStageRemoteSpend tests that when a remite commitment -// confirms, and the remote spends the output using the success tx, we -// properly detect this and extract the preimage. +// TestHtlcTimeoutSecondStageRemoteSpend tests that when a remote commitment +// confirms, and the remote spends the output using the success tx, we properly +// detect this and extract the preimage. func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { commitOutpoint := wire.OutPoint{Index: 2} @@ -870,10 +906,6 @@ func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { } checkpoints := []checkpoint{ - { - // Output should be handed off to the nursery. - incubating: true, - }, { // We send a confirmation for the remote's second layer // success transcation. @@ -919,7 +951,7 @@ func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) { // After the sweep has confirmed, we expect the // checkpoint to be resolved, and with the above // report. - incubating: true, + incubating: false, resolved: true, reports: []*channeldb.ResolverReport{ claim, @@ -1298,6 +1330,8 @@ func TestHtlcTimeoutSecondStageSweeperRemoteSpend(t *testing.T) { func testHtlcTimeout(t *testing.T, resolution lnwallet.OutgoingHtlcResolution, checkpoints []checkpoint) { + t.Helper() + defer timeout()() // We first run the resolver from start to finish, ensuring it gets diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index 0d53b07b66..90ad2b1c87 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -30,6 +30,7 @@ type Registry interface { NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey models.CircuitKey, hodlChan chan<- interface{}, + wireCustomRecords lnwire.CustomRecords, payload invoices.Payload) (invoices.HtlcResolution, error) // HodlUnsubscribeAll unsubscribes from all htlc resolutions. diff --git a/contractcourt/mock_htlcnotifier_test.go b/contractcourt/mock_htlcnotifier_test.go index 4f7aadbab3..52bd18676a 100644 --- a/contractcourt/mock_htlcnotifier_test.go +++ b/contractcourt/mock_htlcnotifier_test.go @@ -10,5 +10,6 @@ type mockHTLCNotifier struct { } func (m *mockHTLCNotifier) NotifyFinalHtlcEvent(key models.CircuitKey, - info channeldb.FinalHtlcInfo) { //nolint:whitespace + info channeldb.FinalHtlcInfo) { + } diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 7acac67ec5..5c75185623 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -26,6 +26,7 @@ type mockRegistry struct { func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey models.CircuitKey, hodlChan chan<- interface{}, + wireCustomRecords lnwire.CustomRecords, payload invoices.Payload) (invoices.HtlcResolution, error) { r.notifyChan <- notifyExitHopData{ diff --git a/contractcourt/taproot_briefcase.go b/contractcourt/taproot_briefcase.go index 5931a4556f..4b703dd295 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,67 @@ 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] + + // HtlcBlobs is an optikonal record that contains the opaque blobs for + // the set of active HTLCs on the commitment transaction. + HtlcBlobs tlv.OptionalRecordT[tlv.TlvType4, htlcAuxBlobs] } +// 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()) + }, + ) + t.HtlcBlobs.WhenSome(func(r tlv.RecordT[tlv.TlvType4, htlcAuxBlobs]) { + 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 +99,35 @@ 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() + htlcBlobs := t.HtlcBlobs.Zero() + + records := append( + t.DecodeRecords(), settledCommitBlob.Record(), + breachedCommitBlob.Record(), htlcBlobs.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) + } + if v, ok := typeMap[t.HtlcBlobs.TlvType()]; ok && v == nil { + t.HtlcBlobs = tlv.SomeRecordT(htlcBlobs) + } + + return nil } // resolverCtrlBlocks is a map of resolver IDs to their corresponding control @@ -216,8 +267,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 +311,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 +320,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 +329,7 @@ func ctrlBlockDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return err } - *typ = &ctrlBlocks + *typ = ctrlBlocks return nil } @@ -286,28 +337,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 +411,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 +573,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 +582,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 +591,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 +600,7 @@ func tapTweaksDecoder(r io.Reader, val any, _ *[8]byte, l uint64) error { return err } - *typ = &tapTweaks + *typ = tapTweaks return nil } @@ -565,27 +608,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 +659,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. @@ -659,3 +695,110 @@ func (t *tapTweaks) Decode(r io.Reader) error { return stream.Decode(r) } + +// htlcAuxBlobs is a map of resolver IDs to their corresponding HTLC blobs. +// This is used to store the resolution blobs for HTLCs that are not yet +// resolved. +type htlcAuxBlobs map[resolverID]tlv.Blob + +// newAuxHtlcBlobs returns a new instance of the htlcAuxBlobs struct. +func newAuxHtlcBlobs() htlcAuxBlobs { + return make(htlcAuxBlobs) +} + +// Encode encodes the set of HTLC blobs into the target writer. +func (h *htlcAuxBlobs) Encode(w io.Writer) error { + var buf [8]byte + + numBlobs := uint64(len(*h)) + if err := tlv.WriteVarInt(w, numBlobs, &buf); err != nil { + return err + } + + for id, blob := range *h { + if _, err := w.Write(id[:]); err != nil { + return err + } + + if err := varBytesEncoder(w, &blob, &buf); err != nil { + return err + } + } + + return nil +} + +// Decode decodes the set of HTLC blobs from the target reader. +func (h *htlcAuxBlobs) Decode(r io.Reader) error { + var buf [8]byte + + numBlobs, err := tlv.ReadVarInt(r, &buf) + if err != nil { + return err + } + + for i := uint64(0); i < numBlobs; i++ { + var id resolverID + if _, err := io.ReadFull(r, id[:]); err != nil { + return err + } + + var blob tlv.Blob + if err := varBytesDecoder(r, &blob, &buf, 0); err != nil { + return err + } + + (*h)[id] = blob + } + + return nil +} + +// eHtlcAuxBlobsEncoder is a custom TLV encoder for the htlcAuxBlobs struct. +func htlcAuxBlobsEncoder(w io.Writer, val any, _ *[8]byte) error { + if t, ok := val.(*htlcAuxBlobs); ok { + return (*t).Encode(w) + } + + return tlv.NewTypeForEncodingErr(val, "htlcAuxBlobs") +} + +// dHtlcAuxBlobsDecoder is a custom TLV decoder for the htlcAuxBlobs struct. +func htlcAuxBlobsDecoder(r io.Reader, val any, _ *[8]byte, + l uint64) error { + + if typ, ok := val.(*htlcAuxBlobs); ok { + blobReader := io.LimitReader(r, int64(l)) + + htlcBlobs := newAuxHtlcBlobs() + err := htlcBlobs.Decode(blobReader) + if err != nil { + return err + } + + *typ = htlcBlobs + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "htlcAuxBlobs", l, l) +} + +// Record returns a tlv.Record for the htlcAuxBlobs struct. +func (h *htlcAuxBlobs) Record() tlv.Record { + recordSize := func() uint64 { + var ( + b bytes.Buffer + buf [8]byte + ) + if err := htlcAuxBlobsEncoder(&b, h, &buf); err != nil { + panic(err) + } + + return uint64(len(b.Bytes())) + } + + return tlv.MakeDynamicRecord( + 0, h, recordSize, htlcAuxBlobsEncoder, htlcAuxBlobsDecoder, + ) +} diff --git a/contractcourt/taproot_briefcase_test.go b/contractcourt/taproot_briefcase_test.go index 38471ed744..441aebf1d7 100644 --- a/contractcourt/taproot_briefcase_test.go +++ b/contractcourt/taproot_briefcase_test.go @@ -5,7 +5,9 @@ import ( "math/rand" "testing" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" + "pgregory.net/rapid" ) func randResolverCtrlBlocks(t *testing.T) resolverCtrlBlocks { @@ -52,6 +54,25 @@ func randHtlcTweaks(t *testing.T) htlcTapTweaks { return tweaks } +func randHtlcAuxBlobs(t *testing.T) htlcAuxBlobs { + numBlobs := rand.Int() % 256 + blobs := make(htlcAuxBlobs, numBlobs) + + for i := 0; i < numBlobs; i++ { + var id resolverID + _, err := rand.Read(id[:]) + require.NoError(t, err) + + var blob [100]byte + _, err = rand.Read(blob[:]) + require.NoError(t, err) + + blobs[id] = blob[:] + } + + return blobs +} + // TestTaprootBriefcase tests the encode/decode methods of the taproot // briefcase extension. func TestTaprootBriefcase(t *testing.T) { @@ -69,19 +90,32 @@ 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[:]), + ), + HtlcBlobs: tlv.SomeRecordT( + tlv.NewRecordT[tlv.TlvType4](randHtlcAuxBlobs(t)), + ), } var b bytes.Buffer @@ -92,3 +126,21 @@ func TestTaprootBriefcase(t *testing.T) { require.Equal(t, testCase, &decodedCase) } + +// TestHtlcAuxBlobEncodeDecode tests the encode/decode methods of the HTLC aux +// blobs. +func TestHtlcAuxBlobEncodeDecode(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(t *rapid.T) { + htlcBlobs := rapid.Make[htlcAuxBlobs]().Draw(t, "htlcAuxBlobs") + + var b bytes.Buffer + require.NoError(t, htlcBlobs.Encode(&b)) + + decodedBlobs := newAuxHtlcBlobs() + require.NoError(t, decodedBlobs.Decode(&b)) + + require.Equal(t, htlcBlobs, decodedBlobs) + }) +} diff --git a/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail b/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail new file mode 100644 index 0000000000..86bb07ed75 --- /dev/null +++ b/contractcourt/testdata/rapid/TestHtlcAuxBlobEncodeDecode/TestHtlcAuxBlobEncodeDecode-20240902140253-81338.fail @@ -0,0 +1,78 @@ +# 2024/09/02 14:02:53.354676 [TestHtlcAuxBlobEncodeDecode] [rapid] draw htlcAuxBlobs: contractcourt.htlcAuxBlobs{contractcourt.resolverID{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}:[]uint8{}} +# +v0.4.8#15807814492030881602 +0x5555555555555 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index 4073841628..b7b4d33a8b 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/dev.Dockerfile b/dev.Dockerfile index c0d4572bb4..945e6a5b5d 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -3,7 +3,7 @@ # /make/builder.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.22.5-alpine as builder +FROM golang:1.22.6-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 84fae767f0..1d3051de57 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -20,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/graph" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -82,9 +83,10 @@ var ( // can provide that serve useful when processing a specific network // announcement. type optionalMsgFields struct { - capacity *btcutil.Amount - channelPoint *wire.OutPoint - remoteAlias *lnwire.ShortChannelID + capacity *btcutil.Amount + channelPoint *wire.OutPoint + remoteAlias *lnwire.ShortChannelID + tapscriptRoot fn.Option[chainhash.Hash] } // apply applies the optional fields within the functional options. @@ -115,6 +117,14 @@ func ChannelPoint(op wire.OutPoint) OptionalMsgField { } } +// TapscriptRoot is an optional field that lets the gossiper know of the root of +// the tapscript tree for a custom channel. +func TapscriptRoot(root fn.Option[chainhash.Hash]) OptionalMsgField { + return func(f *optionalMsgFields) { + f.tapscriptRoot = root + } +} + // RemoteAlias is an optional field that lets the gossiper know that a locally // sent channel update is actually an update for the peer that should replace // the ShortChannelID field with the remote's alias. This is only used for @@ -2578,6 +2588,9 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, cp := *nMsg.optionalMsgFields.channelPoint edge.ChannelPoint = cp } + + // Optional tapscript root for custom channels. + edge.TapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot } log.Debugf("Adding edge for short_chan_id: %v", scid.ToUint64()) diff --git a/discovery/syncer.go b/discovery/syncer.go index b6adb447a6..cdb041d99b 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -835,12 +835,6 @@ func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro } g.prevReplyChannelRange = msg - if len(msg.Timestamps) != 0 && - len(msg.Timestamps) != len(msg.ShortChanIDs) { - - return fmt.Errorf("number of timestamps not equal to " + - "number of SCIDs") - } for i, scid := range msg.ShortChanIDs { info := channeldb.NewChannelUpdateInfo( diff --git a/docker/btcd/Dockerfile b/docker/btcd/Dockerfile index 0a54540c8e..0bb29c898d 100644 --- a/docker/btcd/Dockerfile +++ b/docker/btcd/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.5-alpine as builder +FROM golang:1.22.6-alpine as builder LABEL maintainer="Olaoluwa Osuntokun " diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 3090835f3d..3b7f7cf4b2 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -100,12 +100,12 @@ the following commands for your OS: Linux (x86-64) ``` - wget https://dl.google.com/go/go1.22.5.linux-amd64.tar.gz + wget https://dl.google.com/go/go1.22.6.linux-amd64.tar.gz sha256sum go1.22.5.linux-amd64.tar.gz | awk -F " " '{ print $1 }' ``` The final output of the command above should be - `904b924d435eaea086515bc63235b192ea441bd8c9b198c507e85009e6e4c7f0`. If it + `999805bed7d9039ec3da1a53bfbcafc13e367da52aa823cb60b68ba22d44c616`. If it isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install this version of Go. If it matches, then proceed to install Go: ``` @@ -123,7 +123,7 @@ the following commands for your OS: ``` The final output of the command above should be - `8c4587cf3e63c9aefbcafa92818c4d9d51683af93ea687bf6c7508d6fa36f85e`. If it + `b566484fe89a54c525dd1a4cbfec903c1f6e8f0b7b3dbaf94c79bc9145391083`. If it isn't, then the target REPO HAS BEEN MODIFIED, and you shouldn't install this version of Go. If it matches, then proceed to install Go: ``` 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 0000000000..56270c0a77 --- /dev/null +++ b/docs/release-notes/release-notes-0.18.4.md @@ -0,0 +1,124 @@ +# 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 + +* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9134) that would + cause a nil pointer dereference during the probing of a payment request that + does not contain a payment address. + +* [Make the contract resolutions for the channel arbitrator optional]( + https://github.com/lightningnetwork/lnd/pull/9253). + +# 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 in a series of changes: + * https://github.com/lightningnetwork/lnd/pull/9025 + * https://github.com/lightningnetwork/lnd/pull/9030 + * https://github.com/lightningnetwork/lnd/pull/9049 + * https://github.com/lightningnetwork/lnd/pull/9072 + * https://github.com/lightningnetwork/lnd/pull/9095 + * https://github.com/lightningnetwork/lnd/pull/8960 + * https://github.com/lightningnetwork/lnd/pull/9194 + * https://github.com/lightningnetwork/lnd/pull/9288 + +## 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). + +* Compatibility with [`bitcoind + v28.0`](https://github.com/lightningnetwork/lnd/pull/9059) was ensured by + updating the version the CI pipeline is running against. + +## 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 + +* [A new method](https://github.com/lightningnetwork/lnd/pull/9195) + `AssertTxnsNotInMempool` has been added to `lntest` package to allow batch + exclusion check in itest. + +# Technical and Architectural Updates +## BOLT Spec Updates + +## Testing +## Database + +## Code Health + +## Tooling and Documentation + +# Contributors (Alphabetical Order) + +* Elle Mouton +* ffranr +* George Tsagkarelis +* Olaoluwa Osuntokun +* Oliver Gugger + diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/feature/default_sets.go b/feature/default_sets.go index 447870b277..4a9b2bf64d 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -92,7 +92,8 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, - lnwire.Bolt11BlindedPathsOptional: { - SetInvoice: {}, // I + lnwire.SimpleTaprootOverlayChansOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N }, } diff --git a/feature/deps.go b/feature/deps.go index 64b4f2fc9c..922f252141 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 0b66539859..89f1d4b6bc 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/aux_funding.go b/funding/aux_funding.go new file mode 100644 index 0000000000..492612145a --- /dev/null +++ b/funding/aux_funding.go @@ -0,0 +1,51 @@ +package funding + +import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/msgmux" +) + +// AuxFundingDescResult is a type alias for a function that returns an optional +// aux funding desc. +type AuxFundingDescResult = fn.Result[fn.Option[lnwallet.AuxFundingDesc]] + +// AuxTapscriptResult is a type alias for a function that returns an optional +// tapscript root. +type AuxTapscriptResult = fn.Result[fn.Option[chainhash.Hash]] + +// AuxFundingController permits the implementation of the funding of custom +// channels types. The controller serves as a MsgEndpoint which allows it to +// intercept custom messages, or even the regular funding messages. The +// controller might also pass along an aux funding desc based on an existing +// pending channel ID. +type AuxFundingController interface { + // Endpoint is the embedded interface that signals that the funding + // controller is also a message endpoint. This'll allow it to handle + // custom messages specific to the funding type. + msgmux.Endpoint + + // DescFromPendingChanID takes a pending channel ID, that may already be + // known due to prior custom channel messages, and maybe returns an aux + // funding desc which can be used to modify how a channel is funded. + DescFromPendingChanID(pid PendingChanID, openChan lnwallet.AuxChanState, + keyRing lntypes.Dual[lnwallet.CommitmentKeyRing], + initiator bool) AuxFundingDescResult + + // DeriveTapscriptRoot takes a pending channel ID and maybe returns a + // tapscript root that should be used when creating any MuSig2 sessions + // for a channel. + DeriveTapscriptRoot(PendingChanID) AuxTapscriptResult + + // ChannelReady is called when a channel has been fully opened (multiple + // confirmations) and is ready to be used. This can be used to perform + // any final setup or cleanup. + ChannelReady(openChan lnwallet.AuxChanState) error + + // ChannelFinalized is called when a channel has been fully finalized. + // In this state, we've received the commitment sig from the remote + // party, so we are safe to broadcast the funding transaction. + ChannelFinalized(PendingChanID) error +} diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index f931b4702a..817709c73d 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/interfaces.go b/funding/interfaces.go index b87705424b..30cae08407 100644 --- a/funding/interfaces.go +++ b/funding/interfaces.go @@ -36,7 +36,8 @@ type aliasHandler interface { GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, error) // AddLocalAlias persists an alias to an underlying alias store. - AddLocalAlias(lnwire.ShortChannelID, lnwire.ShortChannelID, bool) error + AddLocalAlias(lnwire.ShortChannelID, lnwire.ShortChannelID, bool, + bool) error // GetAliases returns the set of aliases given the main SCID of a // channel. This SCID will be an alias for zero-conf channels and will diff --git a/funding/manager.go b/funding/manager.go index 8ad16005c7..2f89c17ad8 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "sync" + "sync/atomic" "time" "github.com/btcsuite/btcd/blockchain" @@ -23,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/discovery" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/graph" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -97,7 +99,6 @@ const ( // you and limitless channel size (apart from 21 million cap). MaxBtcFundingAmountWumbo = btcutil.Amount(1000000000) - // TODO(roasbeef): tune. msgBufferSize = 50 // MaxWaitNumBlocksFundingConf is the maximum number of blocks to wait @@ -287,7 +288,7 @@ type InitFundingMsg struct { // PendingChanID is not all zeroes (the default value), then this will // be the pending channel ID used for the funding flow within the wire // protocol. - PendingChanID [32]byte + PendingChanID PendingChanID // ChannelType allows the caller to use an explicit channel type for the // funding negotiation. This type will only be observed if BOTH sides @@ -317,7 +318,7 @@ type fundingMsg struct { // pendingChannels is a map instantiated per-peer which tracks all active // pending single funded channels indexed by their pending channel identifier, // which is a set of 32-bytes generated via a CSPRNG. -type pendingChannels map[[32]byte]*reservationWithCtx +type pendingChannels map[PendingChanID]*reservationWithCtx // serializedPubKey is used within the FundingManager's activeReservations list // to identify the nodes with which the FundingManager is actively working to @@ -543,6 +544,24 @@ type Config struct { // backed funding flow to not use utxos still being swept by the sweeper // subsystem. IsSweeperOutpoint func(wire.OutPoint) bool + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // AuxFundingController is an optional controller that can be used to + // modify the way we handle certain custom channel types. It's also + // able to automatically handle new custom protocol messages related to + // the funding process. + AuxFundingController fn.Option[AuxFundingController] + + // 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 @@ -568,8 +587,10 @@ type Manager struct { // chanIDNonce is a nonce that's incremented for each new funding // reservation created. - nonceMtx sync.RWMutex - chanIDNonce uint64 + chanIDNonce atomic.Uint64 + + // nonceMtx is a mutex that guards the pendingMusigNonces. + nonceMtx sync.RWMutex // pendingMusigNonces is used to store the musig2 nonce we generate to // send funding locked until we receive a funding locked message from @@ -591,7 +612,7 @@ type Manager struct { // required as mid funding flow, we switch to referencing the channel // by its full channel ID once the commitment transactions have been // signed by both parties. - signedReservations map[lnwire.ChannelID][32]byte + signedReservations map[lnwire.ChannelID]PendingChanID // resMtx guards both of the maps above to ensure that all access is // goroutine safe. @@ -798,24 +819,28 @@ func (f *Manager) rebroadcastFundingTx(c *channeldb.OpenChannel) { } } +// PendingChanID is a type that represents a pending channel ID. This might be +// selected by the caller, but if not, will be automatically selected. +type PendingChanID = [32]byte + // nextPendingChanID returns the next free pending channel ID to be used to // identify a particular future channel funding workflow. -func (f *Manager) nextPendingChanID() [32]byte { - // Obtain a fresh nonce. We do this by encoding the current nonce - // counter, then incrementing it by one. - f.nonceMtx.Lock() - var nonce [8]byte - binary.LittleEndian.PutUint64(nonce[:], f.chanIDNonce) - f.chanIDNonce++ - f.nonceMtx.Unlock() +func (f *Manager) nextPendingChanID() PendingChanID { + // Obtain a fresh nonce. We do this by encoding the incremented nonce. + nextNonce := f.chanIDNonce.Add(1) + + var nonceBytes [8]byte + binary.LittleEndian.PutUint64(nonceBytes[:], nextNonce) // We'll generate the next pending channelID by "encrypting" 32-bytes // of zeroes which'll extract 32 random bytes from our stream cipher. var ( - nextChanID [32]byte + nextChanID PendingChanID zeroes [32]byte ) - salsa20.XORKeyStream(nextChanID[:], zeroes[:], nonce[:], &f.chanIDKey) + salsa20.XORKeyStream( + nextChanID[:], zeroes[:], nonceBytes[:], &f.chanIDKey, + ) return nextChanID } @@ -1045,7 +1070,8 @@ func (f *Manager) reservationCoordinator() { // // NOTE: This MUST be run as a goroutine. func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, - pendingChanID [32]byte, updateChan chan<- *lnrpc.OpenStatusUpdate) { + pendingChanID PendingChanID, + updateChan chan<- *lnrpc.OpenStatusUpdate) { defer f.wg.Done() @@ -1061,9 +1087,20 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, } } + var chanOpts []lnwallet.ChannelOpt + f.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + 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( - nil, channel, nil, + nil, channel, nil, chanOpts..., ) if err != nil { log.Errorf("Unable to create LightningChannel(%v): %v", @@ -1115,7 +1152,7 @@ func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel, // updateChan can be set non-nil to get OpenStatusUpdates. func (f *Manager) stateStep(channel *channeldb.OpenChannel, lnChannel *lnwallet.LightningChannel, - shortChanID *lnwire.ShortChannelID, pendingChanID [32]byte, + shortChanID *lnwire.ShortChannelID, pendingChanID PendingChanID, channelState channelOpeningState, updateChan chan<- *lnrpc.OpenStatusUpdate) error { @@ -1238,14 +1275,14 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel, // advancePendingChannelState waits for a pending channel's funding tx to // confirm, and marks it open in the database when that happens. -func (f *Manager) advancePendingChannelState( - channel *channeldb.OpenChannel, pendingChanID [32]byte) error { +func (f *Manager) advancePendingChannelState(channel *channeldb.OpenChannel, + pendingChanID PendingChanID) error { if channel.IsZeroConf() { // Persist the alias to the alias database. baseScid := channel.ShortChannelID err := f.cfg.AliasManager.AddLocalAlias( - baseScid, baseScid, true, + baseScid, baseScid, true, false, ) if err != nil { return fmt.Errorf("error adding local alias to "+ @@ -1608,6 +1645,23 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, return } + // At this point, if we have an AuxFundingController active, we'll + // check to see if we have a special tapscript root to use in our + // MuSig funding output. + tapscriptRoot, err := fn.MapOptionZ( + f.cfg.AuxFundingController, + func(c AuxFundingController) AuxTapscriptResult { + return c.DeriveTapscriptRoot(msg.PendingChannelID) + }, + ).Unpack() + if err != nil { + err = fmt.Errorf("error deriving tapscript root: %w", err) + log.Error(err) + f.failFundingFlow(peer, cid, err) + + return + } + req := &lnwallet.InitFundingReserveMsg{ ChainHash: &msg.ChainHash, PendingChanID: msg.PendingChannelID, @@ -1624,6 +1678,7 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, ZeroConf: zeroConf, OptionScidAlias: scid, ScidAliasFeature: scidFeatureVal, + TapscriptRoot: tapscriptRoot, } reservation, err := f.cfg.Wallet.InitChannelReservation(req) @@ -1880,6 +1935,8 @@ func (f *Manager) fundeeProcessOpenChannel(peer lnpeer.Peer, log.Debugf("Remote party accepted commitment rendering params: %v", lnutils.SpewLogClosure(params)) + reservation.SetState(lnwallet.SentAcceptChannel) + // With the initiator's contribution recorded, respond with our // contribution in the next message of the workflow. fundingAccept := lnwire.AcceptChannel{ @@ -1940,6 +1997,10 @@ func (f *Manager) funderProcessAcceptChannel(peer lnpeer.Peer, // Update the timestamp once the fundingAcceptMsg has been handled. defer resCtx.updateTimestamp() + if resCtx.reservation.State() != lnwallet.SentOpenChannel { + return + } + log.Infof("Recv'd fundingResponse for pending_id(%x)", pendingChanID[:]) @@ -2243,10 +2304,34 @@ func (f *Manager) waitForPsbt(intent *chanfunding.PsbtIntent, return } + // At this point, we'll see if there's an AuxFundingDesc we + // need to deliver so the funding process can continue + // properly. + auxFundingDesc, err := fn.MapOptionZ( + f.cfg.AuxFundingController, + func(c AuxFundingController) AuxFundingDescResult { + return c.DescFromPendingChanID( + cid.tempChanID, + lnwallet.NewAuxChanState( + resCtx.reservation.ChanState(), + ), + resCtx.reservation.CommitmentKeyRings(), + true, + ) + }, + ).Unpack() + if err != nil { + failFlow("error continuing PSBT flow", err) + return + } + // A non-nil error means we can continue the funding flow. // Notify the wallet so it can prepare everything we need to // continue. - err = resCtx.reservation.ProcessPsbt() + // + // We'll also pass along the aux funding controller as well, + // which may be used to help process the finalized PSBT. + err = resCtx.reservation.ProcessPsbt(auxFundingDesc) if err != nil { failFlow("error continuing PSBT flow", err) return @@ -2341,6 +2426,8 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx, } } + resCtx.reservation.SetState(lnwallet.SentFundingCreated) + if err := resCtx.peer.SendMessage(true, fundingCreated); err != nil { log.Errorf("Unable to send funding complete message: %v", err) f.failFundingFlow(resCtx.peer, cid, err) @@ -2372,11 +2459,14 @@ func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer, // final funding transaction, as well as a signature for our version of // the commitment transaction. So at this point, we can validate the // initiator's commitment transaction, then send our own if it's valid. - // TODO(roasbeef): make case (p vs P) consistent throughout fundingOut := msg.FundingPoint log.Infof("completing pending_id(%x) with ChannelPoint(%v)", pendingChanID[:], fundingOut) + if resCtx.reservation.State() != lnwallet.SentAcceptChannel { + return + } + // Create the channel identifier without setting the active channel ID. cid := newChanIdentifier(pendingChanID) @@ -2404,16 +2494,38 @@ func (f *Manager) fundeeProcessFundingCreated(peer lnpeer.Peer, } } + // At this point, we'll see if there's an AuxFundingDesc we need to + // deliver so the funding process can continue properly. + auxFundingDesc, err := fn.MapOptionZ( + f.cfg.AuxFundingController, + func(c AuxFundingController) AuxFundingDescResult { + return c.DescFromPendingChanID( + cid.tempChanID, lnwallet.NewAuxChanState( + resCtx.reservation.ChanState(), + ), resCtx.reservation.CommitmentKeyRings(), + true, + ) + }, + ).Unpack() + if err != nil { + log.Errorf("error continuing PSBT flow: %v", err) + f.failFundingFlow(peer, cid, err) + return + } + // With all the necessary data available, attempt to advance the // funding workflow to the next stage. If this succeeds then the // funding transaction will broadcast after our next message. // CompleteReservationSingle will also mark the channel as 'IsPending' // in the database. + // + // We'll also directly pass in the AuxFunding controller as well, + // which may be used by the reservation system to finalize funding our + // side. completeChan, err := resCtx.reservation.CompleteReservationSingle( - &fundingOut, commitSig, + &fundingOut, commitSig, auxFundingDesc, ) if err != nil { - // TODO(roasbeef): better error logging: peerID, channelID, etc. log.Errorf("unable to complete single reservation: %v", err) f.failFundingFlow(peer, cid, err) return @@ -2614,6 +2726,14 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer, return } + if resCtx.reservation.State() != lnwallet.SentFundingCreated { + err := fmt.Errorf("unable to find reservation for chan_id=%x", + msg.ChanID) + f.failFundingFlow(peer, cid, err) + + return + } + // Create an entry in the local discovery map so we can ensure that we // process the channel confirmation fully before we receive a // channel_ready message. @@ -2709,6 +2829,21 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer, } } + // Before we proceed, if we have a funding hook that wants a + // notification that it's safe to broadcast the funding transaction, + // then we'll send that now. + err = fn.MapOptionZ( + f.cfg.AuxFundingController, + func(controller AuxFundingController) error { + return controller.ChannelFinalized(cid.tempChanID) + }, + ) + if err != nil { + log.Errorf("Failed to inform aux funding controller about "+ + "ChannelPoint(%v) being finalized: %v", fundingPoint, + err) + } + // Now that we have a finalized reservation for this funding flow, // we'll send the to be active channel to the ChainArbitrator so it can // watch for any on-chain actions before the channel has fully @@ -2724,9 +2859,6 @@ func (f *Manager) funderProcessFundingSigned(peer lnpeer.Peer, // Send an update to the upstream client that the negotiation process // is over. - // - // TODO(roasbeef): add abstraction over updates to accommodate - // long-polling, or SSE, etc. upd := &lnrpc.OpenStatusUpdate{ Update: &lnrpc.OpenStatusUpdate_ChanPending{ ChanPending: &lnrpc.PendingUpdate{ @@ -2770,7 +2902,7 @@ type confirmedChannel struct { // channel as closed. The error is only returned for the responder of the // channel flow. func (f *Manager) fundingTimeout(c *channeldb.OpenChannel, - pendingID [32]byte) error { + pendingID PendingChanID) error { // We'll get a timeout if the number of blocks mined since the channel // was initiated reaches MaxWaitNumBlocksFundingConf and we are not the @@ -2891,6 +3023,7 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { if channel.ChanType.IsTaproot() { pkScript, _, err := input.GenTaprootFundingScript( localKey, remoteKey, int64(channel.Capacity), + channel.TapscriptRoot, ) if err != nil { return nil, err @@ -3130,7 +3263,7 @@ func (f *Manager) handleFundingConfirmation( } err = f.cfg.AliasManager.AddLocalAlias( - aliasScid, confChannel.shortChanID, true, + aliasScid, confChannel.shortChanID, true, false, ) if err != nil { return fmt.Errorf("unable to request alias: %w", err) @@ -3296,7 +3429,7 @@ func (f *Manager) sendChannelReady(completeChan *channeldb.OpenChannel, err = f.cfg.AliasManager.AddLocalAlias( alias, completeChan.ShortChannelID, - false, + false, false, ) if err != nil { return err @@ -3431,6 +3564,7 @@ func (f *Manager) addToGraph(completeChan *channeldb.OpenChannel, errChan := f.cfg.SendAnnouncement( ann.chanAnn, discovery.ChannelCapacity(completeChan.Capacity), discovery.ChannelPoint(completeChan.FundingOutpoint), + discovery.TapscriptRoot(completeChan.TapscriptRoot), ) select { case err := <-errChan: @@ -3627,7 +3761,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, // waitForZeroConfChannel is called when the state is addedToGraph with // a zero-conf channel. This will wait for the real confirmation, add the -// confirmed SCID to the graph, and then announce after six confs. +// confirmed SCID to the router graph, and then announce after six confs. func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error { // First we'll check whether the channel is confirmed on-chain. If it // is already confirmed, the chainntnfs subsystem will return with the @@ -3873,7 +4007,7 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen } err = f.cfg.AliasManager.AddLocalAlias( - alias, channel.ShortChannelID, false, + alias, channel.ShortChannelID, false, false, ) if err != nil { log.Errorf("unable to add local alias: %v", @@ -3958,6 +4092,26 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen PubNonce: remoteNonce, }), ) + + // Inform the aux funding controller that the liquidity in the + // custom channel is now ready to be advertised. We potentially + // haven't sent our own channel ready message yet, but other + // than that the channel is ready to count toward available + // liquidity. + err = fn.MapOptionZ( + f.cfg.AuxFundingController, + func(controller AuxFundingController) error { + return controller.ChannelReady( + lnwallet.NewAuxChanState(channel), + ) + }, + ) + if err != nil { + cid := newChanIdentifier(msg.ChanID) + f.sendWarning(peer, cid, err) + + return + } } // The channel_ready message contains the next commitment point we'll @@ -3995,7 +4149,7 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen // channel is now active, thus we change its state to `addedToGraph` to // let the channel start handling routing. func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel, - scid *lnwire.ShortChannelID, pendingChanID [32]byte, + scid *lnwire.ShortChannelID, pendingChanID PendingChanID, updateChan chan<- *lnrpc.OpenStatusUpdate) error { chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint) @@ -4044,6 +4198,19 @@ func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel, log.Debugf("Channel(%v) with ShortChanID %v: successfully "+ "added to graph", chanID, scid) + err = fn.MapOptionZ( + f.cfg.AuxFundingController, + func(controller AuxFundingController) error { + return controller.ChannelReady( + lnwallet.NewAuxChanState(channel), + ) + }, + ) + if err != nil { + return fmt.Errorf("failed notifying aux funding controller "+ + "about channel ready: %w", err) + } + // Give the caller a final update notifying them that the channel is fundingPoint := channel.FundingOutpoint cp := &lnrpc.ChannelPoint{ @@ -4357,9 +4524,9 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, // // We can pass in zeroes for the min and max htlc policy, because we // only use the channel announcement message from the returned struct. - ann, err := f.newChanAnnouncement(localIDKey, remoteIDKey, - localFundingKey, remoteFundingKey, shortChanID, chanID, - 0, 0, nil, chanType, + ann, err := f.newChanAnnouncement( + localIDKey, remoteIDKey, localFundingKey, remoteFundingKey, + shortChanID, chanID, 0, 0, nil, chanType, ) if err != nil { log.Errorf("can't generate channel announcement: %v", err) @@ -4425,7 +4592,6 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, // InitFundingWorkflow sends a message to the funding manager instructing it // to initiate a single funder workflow with the source peer. -// TODO(roasbeef): re-visit blocking nature.. func (f *Manager) InitFundingWorkflow(msg *InitFundingMsg) { f.fundingRequests <- msg } @@ -4519,7 +4685,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { // If the caller specified their own channel ID, then we'll use that. // Otherwise we'll generate a fresh one as normal. This will be used // to track this reservation throughout its lifetime. - var chanID [32]byte + var chanID PendingChanID if msg.PendingChanID == zeroID { chanID = f.nextPendingChanID() } else { @@ -4615,6 +4781,23 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { scidFeatureVal = true } + // At this point, if we have an AuxFundingController active, we'll check + // to see if we have a special tapscript root to use in our MuSig2 + // funding output. + tapscriptRoot, err := fn.MapOptionZ( + f.cfg.AuxFundingController, + func(c AuxFundingController) AuxTapscriptResult { + return c.DeriveTapscriptRoot(chanID) + }, + ).Unpack() + if err != nil { + err = fmt.Errorf("error deriving tapscript root: %w", err) + log.Error(err) + msg.Err <- err + + return + } + req := &lnwallet.InitFundingReserveMsg{ ChainHash: &msg.ChainHash, PendingChanID: chanID, @@ -4654,6 +4837,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { OptionScidAlias: scid, ScidAliasFeature: scidFeatureVal, Memo: msg.Memo, + TapscriptRoot: tapscriptRoot, } reservation, err := f.cfg.Wallet.InitChannelReservation(req) @@ -4805,6 +4989,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { log.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.Peer.Address(), chanID, commitType) + reservation.SetState(lnwallet.SentOpenChannel) + fundingOpen := lnwire.OpenChannel{ ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash, PendingChannelID: chanID, @@ -4942,7 +5128,8 @@ func (f *Manager) pruneZombieReservations() { // cancelReservationCtx does all needed work in order to securely cancel the // reservation. func (f *Manager) cancelReservationCtx(peerKey *btcec.PublicKey, - pendingChanID [32]byte, byRemote bool) (*reservationWithCtx, error) { + pendingChanID PendingChanID, + byRemote bool) (*reservationWithCtx, error) { log.Infof("Cancelling funding reservation for node_key=%x, "+ "chan_id=%x", peerKey.SerializeCompressed(), pendingChanID[:]) @@ -4990,7 +5177,7 @@ func (f *Manager) cancelReservationCtx(peerKey *btcec.PublicKey, // deleteReservationCtx deletes the reservation uniquely identified by the // target public key of the peer, and the specified pending channel ID. func (f *Manager) deleteReservationCtx(peerKey *btcec.PublicKey, - pendingChanID [32]byte) { + pendingChanID PendingChanID) { peerIDKey := newSerializedKey(peerKey) f.resMtx.Lock() @@ -5013,7 +5200,7 @@ func (f *Manager) deleteReservationCtx(peerKey *btcec.PublicKey, // getReservationCtx returns the reservation context for a particular pending // channel ID for a target peer. func (f *Manager) getReservationCtx(peerKey *btcec.PublicKey, - pendingChanID [32]byte) (*reservationWithCtx, error) { + pendingChanID PendingChanID) (*reservationWithCtx, error) { peerIDKey := newSerializedKey(peerKey) f.resMtx.RLock() @@ -5033,7 +5220,7 @@ func (f *Manager) getReservationCtx(peerKey *btcec.PublicKey, // of being funded. After the funding transaction has been confirmed, the // channel will receive a new, permanent channel ID, and will no longer be // considered pending. -func (f *Manager) IsPendingChannel(pendingChanID [32]byte, +func (f *Manager) IsPendingChannel(pendingChanID PendingChanID, peer lnpeer.Peer) bool { peerIDKey := newSerializedKey(peer.IdentityKey()) diff --git a/funding/manager_test.go b/funding/manager_test.go index c4c8b4f36c..a1b33d43ba 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/channelnotifier" "github.com/lightningnetwork/lnd/discovery" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" @@ -161,7 +162,7 @@ func (m *mockAliasMgr) GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, } func (m *mockAliasMgr) AddLocalAlias(lnwire.ShortChannelID, - lnwire.ShortChannelID, bool) error { + lnwire.ShortChannelID, bool, bool) error { return nil } @@ -563,6 +564,12 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, IsSweeperOutpoint: func(wire.OutPoint) bool { return false }, + AuxLeafStore: fn.Some[lnwallet.AuxLeafStore]( + &lnwallet.MockAuxLeafStore{}, + ), + AuxSigner: fn.Some[lnwallet.AuxSigner]( + lnwallet.NewAuxSignerMock(lnwallet.EmptyMockJobHandler), + ), } for _, op := range options { @@ -672,6 +679,8 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { OpenChannelPredicate: chainedAcceptor, DeleteAliasEdge: oldCfg.DeleteAliasEdge, AliasManager: oldCfg.AliasManager, + AuxLeafStore: oldCfg.AuxLeafStore, + AuxSigner: oldCfg.AuxSigner, }) require.NoError(t, err, "failed recreating aliceFundingManager") @@ -4644,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 @@ -4661,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/go.mod b/go.mod index ee656cae08..288f98f727 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,17 @@ require ( github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 github.com/andybalholm/brotli v1.0.4 - github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026 - github.com/btcsuite/btcd/btcec/v2 v2.3.3 + github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 + github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/btcutil/psbt v1.1.8 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd - github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 - github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 - github.com/btcsuite/btcwallet/walletdb v1.4.2 - github.com/btcsuite/btcwallet/wtxmgr v1.5.3 + github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5 + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 + github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 + github.com/btcsuite/btcwallet/walletdb v1.4.4 + github.com/btcsuite/btcwallet/wtxmgr v1.5.4 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 @@ -28,20 +28,20 @@ require ( github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad github.com/jedib0t/go-pretty/v6 v6.2.7 github.com/jessevdk/go-flags v1.4.0 - github.com/jrick/logrotate v1.0.0 + github.com/jrick/logrotate v1.1.2 github.com/kkdai/bstream v1.0.0 github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd github.com/lightninglabs/neutrino/cache v1.1.2 github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 - github.com/lightningnetwork/lnd/fn v1.2.0 + github.com/lightningnetwork/lnd/fn v1.2.3 github.com/lightningnetwork/lnd/healthcheck v1.2.5 github.com/lightningnetwork/lnd/kvdb v1.4.10 github.com/lightningnetwork/lnd/queue v1.1.1 - github.com/lightningnetwork/lnd/sqldb v1.0.3 + github.com/lightningnetwork/lnd/sqldb v1.0.4 github.com/lightningnetwork/lnd/ticker v1.1.1 - github.com/lightningnetwork/lnd/tlv v1.2.3 + github.com/lightningnetwork/lnd/tlv v1.2.6 github.com/lightningnetwork/lnd/tor v1.1.2 github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 github.com/miekg/dns v1.1.43 @@ -62,6 +62,7 @@ require ( google.golang.org/protobuf v1.33.0 gopkg.in/macaroon-bakery.v2 v2.0.1 gopkg.in/macaroon.v2 v2.0.0 + pgregory.net/rapid v1.1.0 ) require ( @@ -72,7 +73,7 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect @@ -156,7 +157,7 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - go.etcd.io/bbolt v1.3.7 // indirect + go.etcd.io/bbolt v1.3.11 // indirect go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/client/v2 v2.305.7 // indirect go.etcd.io/etcd/pkg/v3 v3.5.7 // indirect @@ -209,6 +210,6 @@ replace github.com/lightningnetwork/lnd/sqldb => ./sqldb // If you change this please also update .github/pull_request_template.md, // docs/INSTALL.md and GO_IMAGE in lnrpc/gen_protos_docker.sh. -go 1.21.4 +go 1.22.6 retract v0.0.2 diff --git a/go.sum b/go.sum index cc4ce1bfd9..3b3b0f5d09 100644 --- a/go.sum +++ b/go.sum @@ -73,12 +73,12 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026 h1:s8/96vQSj05bqLl9RyM/eMX8gLtiayEj520TVE4YGy0= -github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 h1:XOZ/wRGHkKv0AqxfDks5IkzaQ1Ge6fq322ZOOG5VIkU= +github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53/go.mod h1:zHK7t7sw8XbsCkD64WePHE3r3k9/XoGAcf6mXV14c64= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0= -github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= @@ -92,18 +92,18 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd h1:QDb8foTCRoXrfoZVEzSYgSde16MJh4gCtCin8OCS0kI= -github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A= -github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 h1:poyHFf7+5+RdxNp5r2T6IBRD7RyraUsYARYbp/7t4D8= -github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4/go.mod h1:GETGDQuyq+VFfH1S/+/7slLM/9aNa4l7P4ejX6dJfb0= -github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 h1:UZo7YRzdHbwhK7Rhv3PO9bXgTxiOH45edK5qdsdiatk= -github.com/btcsuite/btcwallet/wallet/txrules v1.2.1/go.mod h1:MVSqRkju/IGxImXYPfBkG65FgEZYA4fXchheILMVl8g= -github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 h1:nmcKAVTv/cmYrs0A4hbiC6Qw+WTLYy/14SmTt3mLnCo= -github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4/go.mod h1:YqJR8WAAHiKIPesZTr9Cx9Az4fRhRLcJ6GcxzRUZCAc= -github.com/btcsuite/btcwallet/walletdb v1.4.2 h1:zwZZ+zaHo4mK+FAN6KeK85S3oOm+92x2avsHvFAhVBE= -github.com/btcsuite/btcwallet/walletdb v1.4.2/go.mod h1:7ZQ+BvOEre90YT7eSq8bLoxTsgXidUzA/mqbRS114CQ= -github.com/btcsuite/btcwallet/wtxmgr v1.5.3 h1:QrWCio9Leh3DwkWfp+A1SURj8pYn3JuTLv3waP5uEro= -github.com/btcsuite/btcwallet/wtxmgr v1.5.3/go.mod h1:M4nQpxGTXiDlSOODKXboXX7NFthmiBNjzAKKNS7Fhjg= +github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5 h1:zYy233eUBvkF3lq2MUkybEhxhDsrRDSgiToIKN57mtk= +github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5/go.mod h1:1HJXYbjJzgumlnxOC2+ViR1U+gnHWoOn7WeK5OfY1eU= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= +github.com/btcsuite/btcwallet/walletdb v1.4.4 h1:BDel6iT/ltYSIYKs0YbjwnEDi7xR3yzABIsQxN2F1L8= +github.com/btcsuite/btcwallet/walletdb v1.4.4/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/wtxmgr v1.5.4 h1:hJjHy1h/dJwSfD9uDsCwcH21D1iOrus6OrI5gR9E/O0= +github.com/btcsuite/btcwallet/wtxmgr v1.5.4/go.mod h1:lAv0b1Vj9Ig5U8QFm0yiJ9WqPl8yGO/6l7JxdHY1PKE= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= @@ -274,8 +274,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -384,8 +384,9 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= +github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= @@ -450,8 +451,8 @@ github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= -github.com/lightningnetwork/lnd/fn v1.2.0 h1:YTb2m8NN5ZiJAskHeBZAmR1AiPY8SXziIYPAX1VI/ZM= -github.com/lightningnetwork/lnd/fn v1.2.0/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0= +github.com/lightningnetwork/lnd/fn v1.2.3 h1:Q1OrgNSgQynVheBNa16CsKVov1JI5N2AR6G07x9Mles= +github.com/lightningnetwork/lnd/fn v1.2.3/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0= github.com/lightningnetwork/lnd/healthcheck v1.2.5 h1:aTJy5xeBpcWgRtW/PGBDe+LMQEmNm/HQewlQx2jt7OA= github.com/lightningnetwork/lnd/healthcheck v1.2.5/go.mod h1:G7Tst2tVvWo7cx6mSBEToQC5L1XOGxzZTPB29g9Rv2I= github.com/lightningnetwork/lnd/kvdb v1.4.10 h1:vK89IVv1oVH9ubQWU+EmoCQFeVRaC8kfmOrqHbY5zoY= @@ -460,8 +461,8 @@ github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQ github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= -github.com/lightningnetwork/lnd/tlv v1.2.3 h1:If5ibokA/UoCBGuCKaY6Vn2SJU0l9uAbehCnhTZjEP8= -github.com/lightningnetwork/lnd/tlv v1.2.3/go.mod h1:zDkmqxOczP6LaLTvSFDQ1SJUfHcQRCMKFj93dn3eMB8= +github.com/lightningnetwork/lnd/tlv v1.2.6 h1:icvQG2yDr6k3ZuZzfRdG3EJp6pHurcuh3R6dg0gv/Mw= +github.com/lightningnetwork/lnd/tlv v1.2.6/go.mod h1:/CmY4VbItpOldksocmGT4lxiJqRP9oLxwSZOda2kzNQ= github.com/lightningnetwork/lnd/tor v1.1.2 h1:3zv9z/EivNFaMF89v3ciBjCS7kvCj4ZFG7XvD2Qq0/k= github.com/lightningnetwork/lnd/tor v1.1.2/go.mod h1:j7T9uJ2NLMaHwE7GiBGnpYLn4f7NRoTM6qj+ul6/ycA= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= @@ -618,8 +619,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg= @@ -1068,6 +1069,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/graph/builder.go b/graph/builder.go index 82a36eb36a..3c882058bf 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -11,12 +11,14 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnutils" @@ -1090,8 +1092,8 @@ func (b *Builder) addZombieEdge(chanID uint64) error { // segwit v1 (taproot) channels. // // TODO(roasbeef: export and use elsewhere? -func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, - chanFeatures []byte) ([]byte, error) { +func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, chanFeatures []byte, + tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, error) { legacyFundingScript := func() ([]byte, error) { witnessScript, err := input.GenMultiSigScript( @@ -1138,12 +1140,14 @@ func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, } fundingScript, _, err := input.GenTaprootFundingScript( - pubKey1, pubKey2, 0, + pubKey1, pubKey2, 0, tapscriptRoot, ) if err != nil { return nil, err } + // TODO(roasbeef): add tapscript root to gossip v1.5 + return fundingScript, nil } @@ -1268,7 +1272,7 @@ func (b *Builder) processUpdate(msg interface{}, // reality. fundingPkScript, err := makeFundingScript( msg.BitcoinKey1Bytes[:], msg.BitcoinKey2Bytes[:], - msg.Features, + msg.Features, msg.TapscriptRoot, ) if err != nil { return err diff --git a/htlcswitch/interceptable_switch.go b/htlcswitch/interceptable_switch.go index 0ac19a36cd..71302bf075 100644 --- a/htlcswitch/interceptable_switch.go +++ b/htlcswitch/interceptable_switch.go @@ -9,8 +9,10 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" ) @@ -93,7 +95,7 @@ type InterceptableSwitch struct { type interceptedPackets struct { packets []*htlcPacket - linkQuit chan struct{} + linkQuit <-chan struct{} isReplay bool } @@ -109,6 +111,10 @@ const ( // FwdActionFail fails the intercepted packet back to the sender. FwdActionFail + + // FwdActionResumeModified forwards the intercepted packet to the switch + // with modifications. + FwdActionResumeModified ) // FwdResolution defines the action to be taken on an intercepted packet. @@ -123,6 +129,18 @@ type FwdResolution struct { // FwdActionSettle. Preimage lntypes.Preimage + // InAmountMsat is the amount that is to be used for validating if + // Action is FwdActionResumeModified. + InAmountMsat fn.Option[lnwire.MilliSatoshi] + + // OutAmountMsat is the amount that is to be used for forwarding if + // Action is FwdActionResumeModified. + OutAmountMsat fn.Option[lnwire.MilliSatoshi] + + // OutWireCustomRecords is the custom records that are to be used for + // forwarding if Action is FwdActionResumeModified. + OutWireCustomRecords fn.Option[lnwire.CustomRecords] + // FailureMessage is the encrypted failure message that is to be passed // back to the sender if action is FwdActionFail. FailureMessage []byte @@ -387,6 +405,8 @@ func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) { }) } +// resolve processes a HTLC given the resolution type specified by the +// intercepting client. func (s *InterceptableSwitch) resolve(res *FwdResolution) error { intercepted, err := s.heldHtlcSet.pop(res.Key) if err != nil { @@ -397,6 +417,12 @@ func (s *InterceptableSwitch) resolve(res *FwdResolution) error { case FwdActionResume: return intercepted.Resume() + case FwdActionResumeModified: + return intercepted.ResumeModified( + res.InAmountMsat, res.OutAmountMsat, + res.OutWireCustomRecords, + ) + case FwdActionSettle: return intercepted.Settle(res.Preimage) @@ -439,8 +465,8 @@ func (s *InterceptableSwitch) Resolve(res *FwdResolution) error { // interceptor. If the interceptor signals the resume action, the htlcs are // forwarded to the switch. The link's quit signal should be provided to allow // cancellation of forwarding during link shutdown. -func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool, - packets ...*htlcPacket) error { +func (s *InterceptableSwitch) ForwardPackets(linkQuit <-chan struct{}, + isReplay bool, packets ...*htlcPacket) error { // Synchronize with the main event loop. This should be light in the // case where there is no interceptor. @@ -620,15 +646,16 @@ func (f *interceptedForward) Packet() InterceptedPacket { ChanID: f.packet.incomingChanID, HtlcID: f.packet.incomingHTLCID, }, - OutgoingChanID: f.packet.outgoingChanID, - Hash: f.htlc.PaymentHash, - OutgoingExpiry: f.htlc.Expiry, - OutgoingAmount: f.htlc.Amount, - IncomingAmount: f.packet.incomingAmount, - IncomingExpiry: f.packet.incomingTimeout, - CustomRecords: f.packet.customRecords, - OnionBlob: f.htlc.OnionBlob, - AutoFailHeight: f.autoFailHeight, + OutgoingChanID: f.packet.outgoingChanID, + Hash: f.htlc.PaymentHash, + OutgoingExpiry: f.htlc.Expiry, + OutgoingAmount: f.htlc.Amount, + IncomingAmount: f.packet.incomingAmount, + IncomingExpiry: f.packet.incomingTimeout, + InOnionCustomRecords: f.packet.inOnionCustomRecords, + OnionBlob: f.htlc.OnionBlob, + AutoFailHeight: f.autoFailHeight, + InWireCustomRecords: f.packet.inWireCustomRecords, } } @@ -639,6 +666,72 @@ func (f *interceptedForward) Resume() error { return f.htlcSwitch.ForwardPackets(nil, f.packet) } +// ResumeModified resumes the default behavior with field modifications. The +// input amount (if provided) specifies that the value of the inbound HTLC +// should be interpreted differently from the on-chain amount during further +// validation. The presence of an output amount and/or custom records indicates +// that those values should be modified on the outgoing HTLC. +func (f *interceptedForward) ResumeModified( + inAmountMsat fn.Option[lnwire.MilliSatoshi], + outAmountMsat fn.Option[lnwire.MilliSatoshi], + outWireCustomRecords fn.Option[lnwire.CustomRecords]) error { + + // Convert the optional custom records to the correct type and validate + // them. + validatedRecords, err := fn.MapOptionZ( + outWireCustomRecords, + func(cr lnwire.CustomRecords) fn.Result[lnwire.CustomRecords] { + if len(cr) == 0 { + return fn.Ok[lnwire.CustomRecords](nil) + } + + // Type cast and validate custom records. + err := cr.Validate() + if err != nil { + return fn.Err[lnwire.CustomRecords]( + fmt.Errorf("failed to validate "+ + "custom records: %w", err), + ) + } + + return fn.Ok(cr) + }, + ).Unpack() + if err != nil { + return fmt.Errorf("failed to encode custom records: %w", + err) + } + + // Set the incoming amount, if it is provided, on the packet. + inAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) { + f.packet.incomingAmount = amount + }) + + // Modify the wire message contained in the packet. + switch htlc := f.packet.htlc.(type) { + case *lnwire.UpdateAddHTLC: + outAmountMsat.WhenSome(func(amount lnwire.MilliSatoshi) { + f.packet.amount = amount + htlc.Amount = amount + }) + + if len(validatedRecords) > 0 { + htlc.CustomRecords = validatedRecords + } + + case *lnwire.UpdateFulfillHTLC: + if len(validatedRecords) > 0 { + htlc.CustomRecords = validatedRecords + } + } + + log.Tracef("Forwarding packet %v", lnutils.SpewLogClosure(f.packet)) + + // Forward to the switch. A link quit channel isn't needed, because we + // are on a different thread now. + return f.htlcSwitch.ForwardPackets(nil, f.packet) +} + // Fail notifies the intention to Fail an existing hold forward with an // encrypted failure reason. func (f *interceptedForward) Fail(reason []byte) error { diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 1311373a17..72143bc45a 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -13,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/tlv" ) // InvoiceDatabase is an interface which represents the persistent subsystem @@ -32,6 +33,7 @@ type InvoiceDatabase interface { NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey models.CircuitKey, hodlChan chan<- interface{}, + wireCustomRecords lnwire.CustomRecords, payload invoices.Payload) (invoices.HtlcResolution, error) // CancelInvoice attempts to cancel the invoice corresponding to the @@ -278,6 +280,16 @@ type ChannelLink interface { // have buffered messages. AttachMailBox(MailBox) + // FundingCustomBlob returns the custom funding blob of the channel that + // this link is associated with. The funding blob represents static + // information about the channel that was created at channel funding + // time. + FundingCustomBlob() fn.Option[tlv.Blob] + + // CommitmentCustomBlob returns the custom blob of the current local + // commitment of the channel that this link is associated with. + CommitmentCustomBlob() fn.Option[tlv.Blob] + // Start/Stop are used to initiate the start/stop of the channel link // functioning. Start() error @@ -331,7 +343,7 @@ type InterceptableHtlcForwarder interface { type ForwardInterceptor func(InterceptedPacket) error // InterceptedPacket contains the relevant information for the interceptor about -// an htlc. +// an HTLC. type InterceptedPacket struct { // IncomingCircuit contains the incoming channel and htlc id of the // packet. @@ -357,13 +369,17 @@ type InterceptedPacket struct { // IncomingAmount is the amount of the accepted htlc. IncomingAmount lnwire.MilliSatoshi - // CustomRecords are user-defined records in the custom type range that - // were included in the payload. - CustomRecords record.CustomSet + // InOnionCustomRecords are user-defined records in the custom type + // range that were included in the payload. + InOnionCustomRecords record.CustomSet // OnionBlob is the onion packet for the next hop OnionBlob [lnwire.OnionPacketSize]byte + // InWireCustomRecords are user-defined p2p wire message records that + // were defined by the peer that forwarded this HTLC to us. + InWireCustomRecords lnwire.CustomRecords + // AutoFailHeight is the block height at which this intercept will be // failed back automatically. AutoFailHeight int32 @@ -383,6 +399,12 @@ type InterceptedForward interface { // this htlc which usually means forward it. Resume() error + // ResumeModified notifies the intention to resume an existing hold + // forward with modified fields. + ResumeModified(inAmountMsat, + outAmountMsat fn.Option[lnwire.MilliSatoshi], + outWireCustomRecords fn.Option[lnwire.CustomRecords]) error + // Settle notifies the intention to settle an existing hold // forward with a given preimage. Settle(lntypes.Preimage) error diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 337ce636cd..2e2f104af7 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -31,6 +31,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/queue" "github.com/lightningnetwork/lnd/ticker" + "github.com/lightningnetwork/lnd/tlv" ) func init() { @@ -100,7 +101,7 @@ type ChannelLinkConfig struct { // switch. The function returns and error in case it fails to send one or // more packets. The link's quit signal should be provided to allow // cancellation of forwarding during link shutdown. - ForwardPackets func(chan struct{}, bool, ...*htlcPacket) error + ForwardPackets func(<-chan struct{}, bool, ...*htlcPacket) error // DecodeHopIterators facilitates batched decoding of HTLC Sphinx onion // blobs, which are then used to inform how to forward an HTLC. @@ -385,8 +386,10 @@ type channelLink struct { // our next CommitSig. incomingCommitHooks hookMap - wg sync.WaitGroup - quit chan struct{} + // ContextGuard is a helper that encapsulates a wait group and quit + // channel and allows contexts that either block or cancel on those + // depending on the use case. + *fn.ContextGuard } // hookMap is a data structure that is used to track the hooks that need to be @@ -441,7 +444,8 @@ func (m *hookMap) invoke() { // hodlHtlc contains htlc data that is required for resolution. type hodlHtlc struct { - pd *lnwallet.PaymentDescriptor + add lnwire.UpdateAddHTLC + sourceRef channeldb.AddRef obfuscator hop.ErrorEncrypter } @@ -466,7 +470,7 @@ func NewChannelLink(cfg ChannelLinkConfig, flushHooks: newHookMap(), outgoingCommitHooks: newHookMap(), incomingCommitHooks: newHookMap(), - quit: make(chan struct{}), + ContextGuard: fn.NewContextGuard(), } } @@ -545,7 +549,7 @@ func (l *channelLink) Start() error { l.updateFeeTimer = time.NewTimer(l.randomFeeUpdateTimeout()) - l.wg.Add(1) + l.Wg.Add(1) go l.htlcManager() return nil @@ -585,8 +589,8 @@ func (l *channelLink) Stop() { l.hodlQueue.Stop() } - close(l.quit) - l.wg.Wait() + close(l.Quit) + l.Wg.Wait() // Now that the htlcManager has completely exited, reset the packet // courier. This allows the mailbox to revaluate any lingering Adds that @@ -611,7 +615,7 @@ func (l *channelLink) Stop() { // WaitForShutdown blocks until the link finishes shutting down, which includes // termination of all dependent goroutines. func (l *channelLink) WaitForShutdown() { - l.wg.Wait() + l.Wg.Wait() } // EligibleToForward returns a bool indicating if the channel is able to @@ -672,7 +676,7 @@ func (l *channelLink) IsFlushing(linkDirection LinkDirection) bool { func (l *channelLink) OnFlushedOnce(hook func()) { select { case l.flushHooks.newTransients <- hook: - case <-l.quit: + case <-l.Quit: } } @@ -691,7 +695,7 @@ func (l *channelLink) OnCommitOnce(direction LinkDirection, hook func()) { select { case queue <- hook: - case <-l.quit: + case <-l.Quit: } } @@ -900,8 +904,10 @@ func (l *channelLink) syncChanStates() error { // We've just received a ChanSync message from the remote // party, so we'll process the message in order to determine // if we need to re-transmit any messages to the remote party. + ctx, cancel := l.WithCtxQuitNoTimeout() + defer cancel() msgsToReSend, openedCircuits, closedCircuits, err = - l.channel.ProcessChanSyncMsg(remoteChanSyncMsg) + l.channel.ProcessChanSyncMsg(ctx, remoteChanSyncMsg) if err != nil { return err } @@ -930,7 +936,7 @@ func (l *channelLink) syncChanStates() error { l.cfg.Peer.SendMessage(false, msg) } - case <-l.quit: + case <-l.Quit: return ErrLinkShuttingDown } @@ -958,7 +964,7 @@ func (l *channelLink) resolveFwdPkgs() error { // If any of our reprocessing steps require an update to the commitment // txn, we initiate a state transition to capture all relevant changes. - if l.channel.PendingLocalUpdateCount() > 0 { + if l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote) > 0 { return l.updateCommitTx() } @@ -991,15 +997,7 @@ func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) error { // If the package is fully acked but not completed, it must still have // settles and fails to propagate. if !fwdPkg.SettleFailFilter.IsFull() { - settleFails, err := lnwallet.PayDescsFromRemoteLogUpdates( - fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails, - ) - if err != nil { - l.log.Errorf("unable to process remote log updates: %v", - err) - return err - } - l.processRemoteSettleFails(fwdPkg, settleFails) + l.processRemoteSettleFails(fwdPkg) } // Finally, replay *ALL ADDS* in this forwarding package. The @@ -1007,15 +1005,7 @@ func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) error { // shove the entire, original set of adds down the pipeline so that the // batch of adds presented to the sphinx router does not ever change. if !fwdPkg.AckFilter.IsFull() { - adds, err := lnwallet.PayDescsFromRemoteLogUpdates( - fwdPkg.Source, fwdPkg.Height, fwdPkg.Adds, - ) - if err != nil { - l.log.Errorf("unable to process remote log updates: %v", - err) - return err - } - l.processRemoteAdds(fwdPkg, adds) + l.processRemoteAdds(fwdPkg) // If the link failed during processing the adds, we must // return to ensure we won't attempted to update the state @@ -1036,7 +1026,7 @@ func (l *channelLink) resolveFwdPkg(fwdPkg *channeldb.FwdPkg) error { // // NOTE: This MUST be run as a goroutine. func (l *channelLink) fwdPkgGarbager() { - defer l.wg.Done() + defer l.Wg.Done() l.cfg.FwdPkgGCTicker.Resume() defer l.cfg.FwdPkgGCTicker.Stop() @@ -1053,7 +1043,7 @@ func (l *channelLink) fwdPkgGarbager() { err) continue } - case <-l.quit: + case <-l.Quit: return } } @@ -1086,6 +1076,83 @@ func (l *channelLink) loadAndRemove() error { return l.channel.RemoveFwdPkgs(removeHeights...) } +// handleChanSyncErr performs the error handling logic in the case where we +// could not successfully syncChanStates with our channel peer. +func (l *channelLink) handleChanSyncErr(err error) { + l.log.Warnf("error when syncing channel states: %v", err) + + var errDataLoss *lnwallet.ErrCommitSyncLocalDataLoss + + switch { + case errors.Is(err, ErrLinkShuttingDown): + l.log.Debugf("unable to sync channel states, link is " + + "shutting down") + return + + // We failed syncing the commit chains, probably because the remote has + // lost state. We should force close the channel. + case errors.Is(err, lnwallet.ErrCommitSyncRemoteDataLoss): + fallthrough + + // The remote sent us an invalid last commit secret, we should force + // close the channel. + // TODO(halseth): and permanently ban the peer? + case errors.Is(err, lnwallet.ErrInvalidLastCommitSecret): + fallthrough + + // The remote sent us a commit point different from what they sent us + // before. + // TODO(halseth): ban peer? + case errors.Is(err, lnwallet.ErrInvalidLocalUnrevokedCommitPoint): + // We'll fail the link and tell the peer to force close the + // channel. Note that the database state is not updated here, + // but will be updated when the close transaction is ready to + // avoid that we go down before storing the transaction in the + // db. + l.failf( + LinkFailureError{ + code: ErrSyncError, + FailureAction: LinkFailureForceClose, + }, + "unable to synchronize channel states: %v", err, + ) + + // We have lost state and cannot safely force close the channel. Fail + // the channel and wait for the remote to hopefully force close it. The + // remote has sent us its latest unrevoked commitment point, and we'll + // store it in the database, such that we can attempt to recover the + // funds if the remote force closes the channel. + case errors.As(err, &errDataLoss): + err := l.channel.MarkDataLoss( + errDataLoss.CommitPoint, + ) + if err != nil { + l.log.Errorf("unable to mark channel data loss: %v", + err) + } + + // We determined the commit chains were not possible to sync. We + // cautiously fail the channel, but don't force close. + // TODO(halseth): can we safely force close in any cases where this + // error is returned? + case errors.Is(err, lnwallet.ErrCannotSyncCommitChains): + if err := l.channel.MarkBorked(); err != nil { + l.log.Errorf("unable to mark channel borked: %v", err) + } + + // Other, unspecified error. + default: + } + + l.failf( + LinkFailureError{ + code: ErrRecoveryError, + FailureAction: LinkFailureForceNone, + }, + "unable to synchronize channel states: %v", err, + ) +} + // htlcManager is the primary goroutine which drives a channel's commitment // update state-machine in response to messages received via several channels. // This goroutine reads messages from the upstream (remote) peer, and also from @@ -1099,7 +1166,7 @@ func (l *channelLink) loadAndRemove() error { func (l *channelLink) htlcManager() { defer func() { l.cfg.BatchTicker.Stop() - l.wg.Done() + l.Wg.Done() l.log.Infof("exited") }() @@ -1121,88 +1188,7 @@ func (l *channelLink) htlcManager() { if l.cfg.SyncStates { err := l.syncChanStates() if err != nil { - l.log.Warnf("error when syncing channel states: %v", err) - - errDataLoss, localDataLoss := - err.(*lnwallet.ErrCommitSyncLocalDataLoss) - - switch { - case err == ErrLinkShuttingDown: - l.log.Debugf("unable to sync channel states, " + - "link is shutting down") - return - - // We failed syncing the commit chains, probably - // because the remote has lost state. We should force - // close the channel. - case err == lnwallet.ErrCommitSyncRemoteDataLoss: - fallthrough - - // The remote sent us an invalid last commit secret, we - // should force close the channel. - // TODO(halseth): and permanently ban the peer? - case err == lnwallet.ErrInvalidLastCommitSecret: - fallthrough - - // The remote sent us a commit point different from - // what they sent us before. - // TODO(halseth): ban peer? - case err == lnwallet.ErrInvalidLocalUnrevokedCommitPoint: - // We'll fail the link and tell the peer to - // force close the channel. Note that the - // database state is not updated here, but will - // be updated when the close transaction is - // ready to avoid that we go down before - // storing the transaction in the db. - l.fail( - LinkFailureError{ - code: ErrSyncError, - FailureAction: LinkFailureForceClose, //nolint:lll - }, - "unable to synchronize channel "+ - "states: %v", err, - ) - return - - // We have lost state and cannot safely force close the - // channel. Fail the channel and wait for the remote to - // hopefully force close it. The remote has sent us its - // latest unrevoked commitment point, and we'll store - // it in the database, such that we can attempt to - // recover the funds if the remote force closes the - // channel. - case localDataLoss: - err := l.channel.MarkDataLoss( - errDataLoss.CommitPoint, - ) - if err != nil { - l.log.Errorf("unable to mark channel "+ - "data loss: %v", err) - } - - // We determined the commit chains were not possible to - // sync. We cautiously fail the channel, but don't - // force close. - // TODO(halseth): can we safely force close in any - // cases where this error is returned? - case err == lnwallet.ErrCannotSyncCommitChains: - if err := l.channel.MarkBorked(); err != nil { - l.log.Errorf("unable to mark channel "+ - "borked: %v", err) - } - - // Other, unspecified error. - default: - } - - l.fail( - LinkFailureError{ - code: ErrRecoveryError, - FailureAction: LinkFailureForceNone, - }, - "unable to synchronize channel "+ - "states: %v", err, - ) + l.handleChanSyncErr(err) return } } @@ -1259,14 +1245,14 @@ func (l *channelLink) htlcManager() { // If the duplicate keystone error was encountered, we'll fail // without sending an Error message to the peer. case ErrDuplicateKeystone: - l.fail(LinkFailureError{code: ErrCircuitError}, + l.failf(LinkFailureError{code: ErrCircuitError}, "temporary circuit error: %v", err) return // A non-nil error was encountered, send an Error message to // the peer. default: - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "unable to resolve fwd pkgs: %v", err) return } @@ -1274,7 +1260,7 @@ func (l *channelLink) htlcManager() { // With our link's in-memory state fully reconstructed, spawn a // goroutine to manage the reclamation of disk space occupied by // completed forwarding packages. - l.wg.Add(1) + l.Wg.Add(1) go l.fwdPkgGarbager() } @@ -1290,15 +1276,19 @@ func (l *channelLink) htlcManager() { // the batch ticker so that it can be cleared. Otherwise pause // the ticker to prevent waking up the htlcManager while the // batch is empty. - if l.channel.PendingLocalUpdateCount() > 0 { + numUpdates := l.channel.NumPendingUpdates( + lntypes.Local, lntypes.Remote, + ) + if numUpdates > 0 { l.cfg.BatchTicker.Resume() l.log.Tracef("BatchTicker resumed, "+ - "PendingLocalUpdateCount=%d", - l.channel.PendingLocalUpdateCount()) + "NumPendingUpdates(Local, Remote)=%d", + numUpdates, + ) } else { l.cfg.BatchTicker.Pause() l.log.Trace("BatchTicker paused due to zero " + - "PendingLocalUpdateCount") + "NumPendingUpdates(Local, Remote)") } select { @@ -1405,7 +1395,7 @@ func (l *channelLink) htlcManager() { } case <-l.cfg.PendingCommitTicker.Ticks(): - l.fail( + l.failf( LinkFailureError{ code: ErrRemoteUnresponsive, FailureAction: LinkFailureDisconnect, @@ -1438,23 +1428,23 @@ func (l *channelLink) htlcManager() { // If the duplicate keystone error was encountered, // fail back gracefully. case ErrDuplicateKeystone: - l.fail(LinkFailureError{code: ErrCircuitError}, - fmt.Sprintf("process hodl queue: "+ - "temporary circuit error: %v", - err, - ), + l.failf(LinkFailureError{ + code: ErrCircuitError, + }, "process hodl queue: "+ + "temporary circuit error: %v", + err, ) // Send an Error message to the peer. default: - l.fail(LinkFailureError{code: ErrInternalError}, - fmt.Sprintf("process hodl queue: "+ - "unable to update commitment:"+ - " %v", err), + l.failf(LinkFailureError{ + code: ErrInternalError, + }, "process hodl queue: unable to update "+ + "commitment: %v", err, ) } - case <-l.quit: + case <-l.Quit: return } } @@ -1519,7 +1509,9 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, l.log.Debugf("received settle resolution for %v "+ "with outcome: %v", circuitKey, res.Outcome) - return l.settleHTLC(res.Preimage, htlc.pd) + return l.settleHTLC( + res.Preimage, htlc.add.ID, htlc.sourceRef, + ) // For htlc failures, we get the relevant failure message based // on the failure resolution and then fail the htlc. @@ -1529,10 +1521,11 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, // Get the lnwire failure message based on the resolution // result. - failure := getResolutionFailure(res, htlc.pd.Amount) + failure := getResolutionFailure(res, htlc.add.Amount) l.sendHTLCError( - htlc.pd, failure, htlc.obfuscator, true, + htlc.add, htlc.sourceRef, failure, htlc.obfuscator, + true, ) return nil @@ -1656,7 +1649,7 @@ func (l *channelLink) handleDownstreamUpdateAdd(pkt *htlcPacket) error { l.log.Tracef("received downstream htlc: payment_hash=%x, "+ "local_log_index=%v, pend_updates=%v", htlc.PaymentHash[:], index, - l.channel.PendingLocalUpdateCount()) + l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote)) pkt.outgoingChanID = l.ShortChanID() pkt.outgoingHTLCID = index @@ -1862,7 +1855,8 @@ func (l *channelLink) handleDownstreamPkt(pkt *htlcPacket) { // tryBatchUpdateCommitTx updates the commitment transaction if the batch is // full. func (l *channelLink) tryBatchUpdateCommitTx() { - if l.channel.PendingLocalUpdateCount() < uint64(l.cfg.BatchSize) { + pending := l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote) + if pending < uint64(l.cfg.BatchSize) { return } @@ -1938,7 +1932,6 @@ func (l *channelLink) cleanupSpuriousResponse(pkt *htlcPacket) { // direct channel with, updating our respective commitment chains. func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { switch msg := msg.(type) { - case *lnwire.UpdateAddHTLC: if l.IsFlushing(Incoming) { // This is forbidden by the protocol specification. @@ -1960,7 +1953,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // handle message ordering due to concurrency choices. // An issue has been filed to address this here: // https://github.com/lightningnetwork/lnd/issues/8393 - l.fail( + l.failf( LinkFailureError{ code: ErrInvalidUpdate, FailureAction: LinkFailureDisconnect, @@ -1979,7 +1972,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // where we are a relaying node (as the blinding point will // be in the payload when we're the introduction node). if msg.BlindingPoint.IsSome() && l.cfg.DisallowRouteBlinding { - l.fail(LinkFailureError{code: ErrInvalidUpdate}, + l.failf(LinkFailureError{code: ErrInvalidUpdate}, "blinding point included when route blinding "+ "is disabled") @@ -1991,7 +1984,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // without sending a revoke. This would mean that the switch // check would only occur later. if l.isOverexposedWithHtlc(msg, true) { - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "peer sent us an HTLC that exceeded our max "+ "fee exposure") @@ -2003,7 +1996,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // "settle" list in the event that we know the preimage. index, err := l.channel.ReceiveHTLC(msg) if err != nil { - l.fail(LinkFailureError{code: ErrInvalidUpdate}, + l.failf(LinkFailureError{code: ErrInvalidUpdate}, "unable to handle upstream add HTLC: %v", err) return } @@ -2029,7 +2022,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { } if !lockedin { - l.fail( + l.failf( LinkFailureError{code: ErrInvalidUpdate}, "unable to handle upstream settle", ) @@ -2037,7 +2030,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { } if err := l.channel.ReceiveHTLCSettle(pre, idx); err != nil { - l.fail( + l.failf( LinkFailureError{ code: ErrInvalidUpdate, FailureAction: LinkFailureForceClose, @@ -2126,7 +2119,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // message to the usual HTLC fail message. err := l.channel.ReceiveFailHTLC(msg.ID, b.Bytes()) if err != nil { - l.fail(LinkFailureError{code: ErrInvalidUpdate}, + l.failf(LinkFailureError{code: ErrInvalidUpdate}, "unable to handle upstream fail HTLC: %v", err) return } @@ -2164,7 +2157,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { idx := msg.ID err := l.channel.ReceiveFailHTLC(idx, msg.Reason[:]) if err != nil { - l.fail(LinkFailureError{code: ErrInvalidUpdate}, + l.failf(LinkFailureError{code: ErrInvalidUpdate}, "unable to handle upstream fail HTLC: %v", err) return } @@ -2183,7 +2176,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { l.uncommittedPreimages..., ) if err != nil { - l.fail( + l.failf( LinkFailureError{code: ErrInternalError}, "unable to add preimages=%v to cache: %v", l.uncommittedPreimages, err, @@ -2202,10 +2195,20 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We just received a new updates to our local commitment // chain, validate this new commitment, closing the link if // invalid. + auxSigBlob, err := msg.CustomRecords.Serialize() + if err != nil { + l.failf( + LinkFailureError{code: ErrInvalidCommitment}, + "unable to serialize custom records: %v", err, + ) + + return + } err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{ CommitSig: msg.CommitSig, HtlcSigs: msg.HtlcSigs, PartialSig: msg.PartialSig, + AuxSigBlob: auxSigBlob, }) if err != nil { // If we were unable to reconstruct their proposed @@ -2219,7 +2222,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { case *lnwallet.InvalidHtlcSigError: sendData = []byte(err.Error()) } - l.fail( + l.failf( LinkFailureError{ code: ErrInvalidCommitment, FailureAction: LinkFailureForceClose, @@ -2248,7 +2251,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // NOTE: We do not trigger a force close because this // could resolve itself in case our db was just busy // not accepting new transactions. - l.fail( + l.failf( LinkFailureError{ code: ErrInternalError, Warning: true, @@ -2299,7 +2302,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { } select { - case <-l.quit: + case <-l.Quit: return default: } @@ -2332,11 +2335,10 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We now process the message and advance our remote commit // chain. - fwdPkg, adds, settleFails, remoteHTLCs, err := l.channel. - ReceiveRevocation(msg) + fwdPkg, remoteHTLCs, err := l.channel.ReceiveRevocation(msg) if err != nil { // TODO(halseth): force close? - l.fail( + l.failf( LinkFailureError{ code: ErrInvalidRevocation, FailureAction: LinkFailureDisconnect, @@ -2361,7 +2363,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { } select { - case <-l.quit: + case <-l.Quit: return default: } @@ -2376,15 +2378,15 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { &chanID, state.RemoteCommitment.CommitHeight-1, ) if err != nil { - l.fail(LinkFailureError{code: ErrInternalError}, - "unable to queue breach backup: %v", - err) + l.failf(LinkFailureError{ + code: ErrInternalError, + }, "unable to queue breach backup: %v", err) return } } - l.processRemoteSettleFails(fwdPkg, settleFails) - l.processRemoteAdds(fwdPkg, adds) + l.processRemoteSettleFails(fwdPkg) + l.processRemoteAdds(fwdPkg) // If the link failed during processing the adds, we must // return to ensure we won't attempted to update the state @@ -2425,7 +2427,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // indicates something is wrong with our channel state. l.log.Errorf("Unable to determine if fee threshold " + "exceeded") - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "error calculating fee exposure: %v", err) return @@ -2434,7 +2436,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { if isDust { // The proposed fee-rate makes us exceed the fee // threshold. - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "fee threshold exceeded: %v", err) return } @@ -2442,7 +2444,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We received fee update from peer. If we are the initiator we // will fail the channel, if not we will apply the update. if err := l.channel.ReceiveUpdateFee(fee); err != nil { - l.fail(LinkFailureError{code: ErrInvalidUpdate}, + l.failf(LinkFailureError{code: ErrInvalidUpdate}, "error receiving fee update: %v", err) return } @@ -2461,7 +2463,7 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // Error received from remote, MUST fail channel, but should // only print the contents of the error message if all // characters are printable ASCII. - l.fail( + l.failf( LinkFailureError{ code: ErrRemoteError, @@ -2550,14 +2552,14 @@ func (l *channelLink) updateCommitTxOrFail() bool { // A duplicate keystone error should be resolved and is not fatal, so // we won't send an Error message to the peer. case ErrDuplicateKeystone: - l.fail(LinkFailureError{code: ErrCircuitError}, + l.failf(LinkFailureError{code: ErrCircuitError}, "temporary circuit error: %v", err) return false // Any other error is treated results in an Error message being sent to // the peer. default: - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "unable to update commitment: %v", err) return false } @@ -2591,14 +2593,17 @@ func (l *channelLink) updateCommitTx() error { return nil } - newCommit, err := l.channel.SignNextCommitment() + ctx, done := l.WithCtxQuitNoTimeout() + defer done() + + newCommit, err := l.channel.SignNextCommitment(ctx) if err == lnwallet.ErrNoWindow { l.cfg.PendingCommitTicker.Resume() l.log.Trace("PendingCommitTicker resumed") + n := l.channel.NumPendingUpdates(lntypes.Local, lntypes.Remote) l.log.Tracef("revocation window exhausted, unable to send: "+ - "%v, pend_updates=%v, dangling_closes%v", - l.channel.PendingLocalUpdateCount(), + "%v, pend_updates=%v, dangling_closes%v", n, lnutils.SpewLogClosure(l.openedCircuits), lnutils.SpewLogClosure(l.closedCircuits)) @@ -2628,16 +2633,22 @@ func (l *channelLink) updateCommitTx() error { } select { - case <-l.quit: + case <-l.Quit: return ErrLinkShuttingDown default: } + auxBlobRecords, err := lnwire.ParseCustomRecords(newCommit.AuxSigBlob) + if err != nil { + return fmt.Errorf("error parsing aux sigs: %w", err) + } + commitSig := &lnwire.CommitSig{ - ChanID: l.ChanID(), - CommitSig: newCommit.CommitSig, - HtlcSigs: newCommit.HtlcSigs, - PartialSig: newCommit.PartialSig, + ChanID: l.ChanID(), + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, + CustomRecords: auxBlobRecords, } l.cfg.Peer.SendMessage(false, commitSig) @@ -3228,7 +3239,7 @@ func (l *channelLink) handleSwitchPacket(pkt *htlcPacket) error { // NOTE: Part of the ChannelLink interface. func (l *channelLink) HandleChannelUpdate(message lnwire.Message) { select { - case <-l.quit: + case <-l.Quit: // Return early if the link is already in the process of // quitting. It doesn't make sense to hand the message to the // mailbox here. @@ -3290,17 +3301,17 @@ func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error { // the context of the provided forwarding package. Any settles or fails that // have already been acknowledged in the forwarding package will not be sent to // the switch. -func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, - settleFails []*lnwallet.PaymentDescriptor) { - - if len(settleFails) == 0 { +func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg) { + if len(fwdPkg.SettleFails) == 0 { return } l.log.Debugf("settle-fail-filter: %v", fwdPkg.SettleFailFilter) var switchPackets []*htlcPacket - for i, pd := range settleFails { + for i, update := range fwdPkg.SettleFails { + destRef := fwdPkg.DestRef(uint16(i)) + // Skip any settles or fails that have already been // acknowledged by the incoming link that originated the // forwarded Add. @@ -3311,12 +3322,11 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // TODO(roasbeef): rework log entries to a shared // interface. - switch pd.EntryType { - + switch msg := update.UpdateMsg.(type) { // A settle for an HTLC we previously forwarded HTLC has been // received. So we'll forward the HTLC to the switch which will // handle propagating the settle to the prior hop. - case lnwallet.Settle: + case *lnwire.UpdateFulfillHTLC: // If hodl.SettleIncoming is requested, we will not // forward the SETTLE to the switch and will not signal // a free slot on the commitment transaction. @@ -3327,11 +3337,9 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, settlePacket := &htlcPacket{ outgoingChanID: l.ShortChanID(), - outgoingHTLCID: pd.ParentIndex, - destRef: pd.DestRef, - htlc: &lnwire.UpdateFulfillHTLC{ - PaymentPreimage: pd.RPreimage, - }, + outgoingHTLCID: msg.ID, + destRef: &destRef, + htlc: msg, } // Add the packet to the batch to be forwarded, and @@ -3343,7 +3351,7 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // been received. As a result a new slot will be freed up in // our commitment state, so we'll forward this to the switch so // the backwards undo can continue. - case lnwallet.Fail: + case *lnwire.UpdateFailHTLC: // If hodl.SettleIncoming is requested, we will not // forward the FAIL to the switch and will not signal a // free slot on the commitment transaction. @@ -3358,16 +3366,12 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // set on the packet. failPacket := &htlcPacket{ outgoingChanID: l.ShortChanID(), - outgoingHTLCID: pd.ParentIndex, - destRef: pd.DestRef, - htlc: &lnwire.UpdateFailHTLC{ - Reason: lnwire.OpaqueReason( - pd.FailReason, - ), - }, + outgoingHTLCID: msg.ID, + destRef: &destRef, + htlc: msg, } - l.log.Debugf("Failed to send %s", pd.Amount) + l.log.Debugf("Failed to send HTLC with ID=%d", msg.ID) // If the failure message lacks an HMAC (but includes // the 4 bytes for encoding the message and padding @@ -3377,7 +3381,7 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // to an actual error, by encrypting it as if we were // the originating hop. convertedErrorSize := lnwire.FailureMessageLength + 4 - if len(pd.FailReason) == convertedErrorSize { + if len(msg.Reason) == convertedErrorSize { failPacket.convertedError = true } @@ -3400,32 +3404,29 @@ func (l *channelLink) processRemoteSettleFails(fwdPkg *channeldb.FwdPkg, // indicating whether this is the first time these Adds are being processed, or // whether we are reprocessing as a result of a failure or restart. Adds that // have already been acknowledged in the forwarding package will be ignored. -func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, - lockedInHtlcs []*lnwallet.PaymentDescriptor) { - +// +//nolint:funlen +func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) { l.log.Tracef("processing %d remote adds for height %d", - len(lockedInHtlcs), fwdPkg.Height) + len(fwdPkg.Adds), fwdPkg.Height) decodeReqs := make( - []hop.DecodeHopIteratorRequest, 0, len(lockedInHtlcs), + []hop.DecodeHopIteratorRequest, 0, len(fwdPkg.Adds), ) - for _, pd := range lockedInHtlcs { - switch pd.EntryType { - - // TODO(conner): remove type switch? - case lnwallet.Add: + for _, update := range fwdPkg.Adds { + if msg, ok := update.UpdateMsg.(*lnwire.UpdateAddHTLC); ok { // Before adding the new htlc to the state machine, // parse the onion object in order to obtain the // routing information with DecodeHopIterator function // which process the Sphinx packet. - onionReader := bytes.NewReader(pd.OnionBlob) + onionReader := bytes.NewReader(msg.OnionBlob[:]) req := hop.DecodeHopIteratorRequest{ OnionReader: onionReader, - RHash: pd.RHash[:], - IncomingCltv: pd.Timeout, - IncomingAmount: pd.Amount, - BlindingPoint: pd.BlindingPoint, + RHash: msg.PaymentHash[:], + IncomingCltv: msg.Expiry, + IncomingAmount: msg.Amount, + BlindingPoint: msg.BlindingPoint, } decodeReqs = append(decodeReqs, req) @@ -3440,16 +3441,20 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, fwdPkg.ID(), decodeReqs, ) if sphinxErr != nil { - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "unable to decode hop iterators: %v", sphinxErr) return } var switchPackets []*htlcPacket - for i, pd := range lockedInHtlcs { + for i, update := range fwdPkg.Adds { idx := uint16(i) + //nolint:forcetypeassert + add := *update.UpdateMsg.(*lnwire.UpdateAddHTLC) + sourceRef := fwdPkg.SourceRef(idx) + if fwdPkg.State == channeldb.FwdStateProcessed && fwdPkg.AckFilter.Contains(idx) { @@ -3467,11 +3472,6 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // or are able to settle it (and it adheres to our fee related // constraints). - // Fetch the onion blob that was included within this processed - // payment descriptor. - var onionBlob [lnwire.OnionPacketSize]byte - copy(onionBlob[:], pd.OnionBlob) - // Before adding the new htlc to the state machine, parse the // onion object in order to obtain the routing information with // DecodeHopIterator function which process the Sphinx packet. @@ -3480,8 +3480,9 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // If we're unable to process the onion blob then we // should send the malformed htlc error to payment // sender. - l.sendMalformedHTLCError(pd.HtlcIndex, failureCode, - onionBlob[:], pd.SourceRef) + l.sendMalformedHTLCError( + add.ID, failureCode, add.OnionBlob, &sourceRef, + ) l.log.Errorf("unable to decode onion hop "+ "iterator: %v", failureCode) @@ -3525,8 +3526,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // We can't process this htlc, send back // malformed. l.sendMalformedHTLCError( - pd.HtlcIndex, failureCode, - onionBlob[:], pd.SourceRef, + add.ID, failureCode, add.OnionBlob, + &sourceRef, ) continue @@ -3539,8 +3540,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // payloads. Deferring this non-trival effort till a // later date failure := lnwire.NewInvalidOnionPayload(failedType, 0) + l.sendHTLCError( - pd, NewLinkError(failure), obfuscator, false, + add, sourceRef, NewLinkError(failure), + obfuscator, false, ) l.log.Errorf("unable to decode forwarding "+ @@ -3560,8 +3563,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // should send the malformed htlc error to payment // sender. l.sendMalformedHTLCError( - pd.HtlcIndex, failureCode, onionBlob[:], - pd.SourceRef, + add.ID, failureCode, add.OnionBlob, + &sourceRef, ) l.log.Errorf("unable to decode onion "+ @@ -3580,10 +3583,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, l.cfg.DisallowRouteBlinding { failure := lnwire.NewInvalidBlinding( - onionBlob[:], + fn.Some(add.OnionBlob), ) + l.sendHTLCError( - pd, NewLinkError(failure), obfuscator, false, + add, sourceRef, NewLinkError(failure), + obfuscator, false, ) l.log.Error("rejected htlc that uses use as an " + @@ -3596,12 +3601,13 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, switch fwdInfo.NextHop { case hop.Exit: err := l.processExitHop( - pd, obfuscator, fwdInfo, heightNow, pld, + add, sourceRef, obfuscator, fwdInfo, + heightNow, pld, ) if err != nil { - l.fail(LinkFailureError{code: ErrInternalError}, - err.Error(), - ) + l.failf(LinkFailureError{ + code: ErrInternalError, + }, err.Error()) //nolint return } @@ -3631,17 +3637,19 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // Otherwise, it was already processed, we can // can collect it and continue. - addMsg := &lnwire.UpdateAddHTLC{ + outgoingAdd := &lnwire.UpdateAddHTLC{ Expiry: fwdInfo.OutgoingCTLV, Amount: fwdInfo.AmountToForward, - PaymentHash: pd.RHash, + PaymentHash: add.PaymentHash, BlindingPoint: fwdInfo.NextBlinding, } // Finally, we'll encode the onion packet for // the _next_ hop using the hop iterator // decoded for the current hop. - buf := bytes.NewBuffer(addMsg.OnionBlob[0:0]) + buf := bytes.NewBuffer( + outgoingAdd.OnionBlob[0:0], + ) // We know this cannot fail, as this ADD // was marked forwarded in a previous @@ -3650,19 +3658,21 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, inboundFee := l.cfg.FwrdingPolicy.InboundFee + //nolint:lll updatePacket := &htlcPacket{ - incomingChanID: l.ShortChanID(), - incomingHTLCID: pd.HtlcIndex, - outgoingChanID: fwdInfo.NextHop, - sourceRef: pd.SourceRef, - incomingAmount: pd.Amount, - amount: addMsg.Amount, - htlc: addMsg, - obfuscator: obfuscator, - incomingTimeout: pd.Timeout, - outgoingTimeout: fwdInfo.OutgoingCTLV, - customRecords: pld.CustomRecords(), - inboundFee: inboundFee, + incomingChanID: l.ShortChanID(), + incomingHTLCID: add.ID, + outgoingChanID: fwdInfo.NextHop, + sourceRef: &sourceRef, + incomingAmount: add.Amount, + amount: outgoingAdd.Amount, + htlc: outgoingAdd, + obfuscator: obfuscator, + incomingTimeout: add.Expiry, + outgoingTimeout: fwdInfo.OutgoingCTLV, + inOnionCustomRecords: pld.CustomRecords(), + inboundFee: inboundFee, + inWireCustomRecords: add.CustomRecords.Copy(), } switchPackets = append( switchPackets, updatePacket, @@ -3680,7 +3690,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, addMsg := &lnwire.UpdateAddHTLC{ Expiry: fwdInfo.OutgoingCTLV, Amount: fwdInfo.AmountToForward, - PaymentHash: pd.RHash, + PaymentHash: add.PaymentHash, BlindingPoint: fwdInfo.NextBlinding, } @@ -3702,7 +3712,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, ) l.sendHTLCError( - pd, NewLinkError(failure), obfuscator, false, + add, sourceRef, NewLinkError(failure), + obfuscator, false, ) continue } @@ -3718,19 +3729,21 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, if fwdPkg.State == channeldb.FwdStateLockedIn { inboundFee := l.cfg.FwrdingPolicy.InboundFee + //nolint:lll updatePacket := &htlcPacket{ - incomingChanID: l.ShortChanID(), - incomingHTLCID: pd.HtlcIndex, - outgoingChanID: fwdInfo.NextHop, - sourceRef: pd.SourceRef, - incomingAmount: pd.Amount, - amount: addMsg.Amount, - htlc: addMsg, - obfuscator: obfuscator, - incomingTimeout: pd.Timeout, - outgoingTimeout: fwdInfo.OutgoingCTLV, - customRecords: pld.CustomRecords(), - inboundFee: inboundFee, + incomingChanID: l.ShortChanID(), + incomingHTLCID: add.ID, + outgoingChanID: fwdInfo.NextHop, + sourceRef: &sourceRef, + incomingAmount: add.Amount, + amount: addMsg.Amount, + htlc: addMsg, + obfuscator: obfuscator, + incomingTimeout: add.Expiry, + outgoingTimeout: fwdInfo.OutgoingCTLV, + inOnionCustomRecords: pld.CustomRecords(), + inboundFee: inboundFee, + inWireCustomRecords: add.CustomRecords.Copy(), } fwdPkg.FwdFilter.Set(idx) @@ -3745,7 +3758,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, if fwdPkg.State == channeldb.FwdStateLockedIn { err := l.channel.SetFwdFilter(fwdPkg.Height, fwdPkg.FwdFilter) if err != nil { - l.fail(LinkFailureError{code: ErrInternalError}, + l.failf(LinkFailureError{code: ErrInternalError}, "unable to set fwd filter: %v", err) return } @@ -3770,9 +3783,10 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, // processExitHop handles an htlc for which this link is the exit hop. It // returns a boolean indicating whether the commitment tx needs an update. -func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, - obfuscator hop.ErrorEncrypter, fwdInfo hop.ForwardingInfo, - heightNow uint32, payload invoices.Payload) error { +func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC, + sourceRef channeldb.AddRef, obfuscator hop.ErrorEncrypter, + fwdInfo hop.ForwardingInfo, heightNow uint32, + payload invoices.Payload) error { // If hodl.ExitSettle is requested, we will not validate the final hop's // ADD, nor will we settle the corresponding invoice or respond with the @@ -3786,30 +3800,42 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // As we're the exit hop, we'll double check the hop-payload included in // the HTLC to ensure that it was crafted correctly by the sender and // is compatible with the HTLC we were extended. - if pd.Amount < fwdInfo.AmountToForward { + // + // For a special case, if the fwdInfo doesn't have any blinded path + // information, and the incoming HTLC had special extra data, then + // we'll skip this amount check. The invoice acceptor will make sure we + // reject the HTLC if it's not containing the correct amount after + // examining the custom data. + hasBlindedPath := fwdInfo.NextBlinding.IsSome() + customHTLC := len(add.CustomRecords) > 0 && !hasBlindedPath + log.Tracef("Exit hop has_blinded_path=%v custom_htlc_bypass=%v", + hasBlindedPath, customHTLC) + + if !customHTLC && add.Amount < fwdInfo.AmountToForward { l.log.Errorf("onion payload of incoming htlc(%x) has "+ - "incompatible value: expected <=%v, got %v", pd.RHash, - pd.Amount, fwdInfo.AmountToForward) + "incompatible value: expected <=%v, got %v", + add.PaymentHash, add.Amount, fwdInfo.AmountToForward) failure := NewLinkError( - lnwire.NewFinalIncorrectHtlcAmount(pd.Amount), + lnwire.NewFinalIncorrectHtlcAmount(add.Amount), ) - l.sendHTLCError(pd, failure, obfuscator, true) + l.sendHTLCError(add, sourceRef, failure, obfuscator, true) return nil } // We'll also ensure that our time-lock value has been computed // correctly. - if pd.Timeout < fwdInfo.OutgoingCTLV { + if add.Expiry < fwdInfo.OutgoingCTLV { l.log.Errorf("onion payload of incoming htlc(%x) has "+ "incompatible time-lock: expected <=%v, got %v", - pd.RHash[:], pd.Timeout, fwdInfo.OutgoingCTLV) + add.PaymentHash, add.Expiry, fwdInfo.OutgoingCTLV) failure := NewLinkError( - lnwire.NewFinalIncorrectCltvExpiry(pd.Timeout), + lnwire.NewFinalIncorrectCltvExpiry(add.Expiry), ) - l.sendHTLCError(pd, failure, obfuscator, true) + + l.sendHTLCError(add, sourceRef, failure, obfuscator, true) return nil } @@ -3817,16 +3843,16 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // Notify the invoiceRegistry of the exit hop htlc. If we crash right // after this, this code will be re-executed after restart. We will // receive back a resolution event. - invoiceHash := lntypes.Hash(pd.RHash) + invoiceHash := lntypes.Hash(add.PaymentHash) circuitKey := models.CircuitKey{ ChanID: l.ShortChanID(), - HtlcID: pd.HtlcIndex, + HtlcID: add.ID, } event, err := l.cfg.Registry.NotifyExitHopHtlc( - invoiceHash, pd.Amount, pd.Timeout, int32(heightNow), - circuitKey, l.hodlQueue.ChanIn(), payload, + invoiceHash, add.Amount, add.Expiry, int32(heightNow), + circuitKey, l.hodlQueue.ChanIn(), add.CustomRecords, payload, ) if err != nil { return err @@ -3834,7 +3860,8 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // Create a hodlHtlc struct and decide either resolved now or later. htlc := hodlHtlc{ - pd: pd, + add: add, + sourceRef: sourceRef, obfuscator: obfuscator, } @@ -3851,14 +3878,14 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, // settleHTLC settles the HTLC on the channel. func (l *channelLink) settleHTLC(preimage lntypes.Preimage, - pd *lnwallet.PaymentDescriptor) error { + htlcIndex uint64, sourceRef channeldb.AddRef) error { hash := preimage.Hash() l.log.Infof("settling htlc %v as exit hop", hash) err := l.channel.SettleHTLC( - preimage, pd.HtlcIndex, pd.SourceRef, nil, nil, + preimage, htlcIndex, &sourceRef, nil, nil, ) if err != nil { return fmt.Errorf("unable to settle htlc: %w", err) @@ -3876,7 +3903,7 @@ func (l *channelLink) settleHTLC(preimage lntypes.Preimage, // remote peer. l.cfg.Peer.SendMessage(false, &lnwire.UpdateFulfillHTLC{ ChanID: l.ChanID(), - ID: pd.HtlcIndex, + ID: htlcIndex, PaymentPreimage: preimage, }) @@ -3885,7 +3912,7 @@ func (l *channelLink) settleHTLC(preimage lntypes.Preimage, HtlcKey{ IncomingCircuit: models.CircuitKey{ ChanID: l.ShortChanID(), - HtlcID: pd.HtlcIndex, + HtlcID: htlcIndex, }, }, preimage, @@ -3911,7 +3938,7 @@ func (l *channelLink) forwardBatch(replay bool, packets ...*htlcPacket) { filteredPkts = append(filteredPkts, pkt) } - err := l.cfg.ForwardPackets(l.quit, replay, filteredPkts...) + err := l.cfg.ForwardPackets(l.Quit, replay, filteredPkts...) if err != nil { log.Errorf("Unhandled error while reforwarding htlc "+ "settle/fail over htlcswitch: %v", err) @@ -3920,8 +3947,9 @@ func (l *channelLink) forwardBatch(replay bool, packets ...*htlcPacket) { // sendHTLCError functions cancels HTLC and send cancel message back to the // peer from which HTLC was received. -func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, - failure *LinkError, e hop.ErrorEncrypter, isReceive bool) { +func (l *channelLink) sendHTLCError(add lnwire.UpdateAddHTLC, + sourceRef channeldb.AddRef, failure *LinkError, + e hop.ErrorEncrypter, isReceive bool) { reason, err := e.EncryptFirstHop(failure.WireMessage()) if err != nil { @@ -3929,7 +3957,7 @@ func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, return } - err = l.channel.FailHTLC(pd.HtlcIndex, reason, pd.SourceRef, nil, nil) + err = l.channel.FailHTLC(add.ID, reason, &sourceRef, nil, nil) if err != nil { l.log.Errorf("unable cancel htlc: %v", err) return @@ -3938,7 +3966,7 @@ func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, // Send the appropriate failure message depending on whether we're // in a blinded route or not. if err := l.sendIncomingHTLCFailureMsg( - pd.HtlcIndex, e, reason, + add.ID, e, reason, ); err != nil { l.log.Errorf("unable to send HTLC failure: %v", err) return @@ -3958,12 +3986,12 @@ func (l *channelLink) sendHTLCError(pd *lnwallet.PaymentDescriptor, HtlcKey{ IncomingCircuit: models.CircuitKey{ ChanID: l.ShortChanID(), - HtlcID: pd.HtlcIndex, + HtlcID: add.ID, }, }, HtlcInfo{ - IncomingTimeLock: pd.Timeout, - IncomingAmt: pd.Amount, + IncomingTimeLock: add.Expiry, + IncomingAmt: add.Amount, }, eventType, failure, @@ -4022,7 +4050,9 @@ func (l *channelLink) sendIncomingHTLCFailureMsg(htlcIndex uint64, // The specification does not require that we set the onion // blob. - failureMsg := lnwire.NewInvalidBlinding(nil) + failureMsg := lnwire.NewInvalidBlinding( + fn.None[[lnwire.OnionPacketSize]byte](), + ) reason, err := e.EncryptFirstHop(failureMsg) if err != nil { return err @@ -4060,9 +4090,10 @@ func (l *channelLink) sendIncomingHTLCFailureMsg(htlcIndex uint64, // sendMalformedHTLCError helper function which sends the malformed HTLC update // to the payment sender. func (l *channelLink) sendMalformedHTLCError(htlcIndex uint64, - code lnwire.FailCode, onionBlob []byte, sourceRef *channeldb.AddRef) { + code lnwire.FailCode, onionBlob [lnwire.OnionPacketSize]byte, + sourceRef *channeldb.AddRef) { - shaOnionBlob := sha256.Sum256(onionBlob) + shaOnionBlob := sha256.Sum256(onionBlob[:]) err := l.channel.MalformedFailHTLC(htlcIndex, code, shaOnionBlob, sourceRef) if err != nil { l.log.Errorf("unable cancel htlc: %v", err) @@ -4077,13 +4108,14 @@ func (l *channelLink) sendMalformedHTLCError(htlcIndex uint64, }) } -// fail is a function which is used to encapsulate the action necessary for +// failf is a function which is used to encapsulate the action necessary for // properly failing the link. It takes a LinkFailureError, which will be passed // to the OnChannelFailure closure, in order for it to determine if we should // force close the channel, and if we should send an error message to the // remote peer. -func (l *channelLink) fail(linkErr LinkFailureError, - format string, a ...interface{}) { +func (l *channelLink) failf(linkErr LinkFailureError, format string, + a ...interface{}) { + reason := fmt.Errorf(format, a...) // Return if we have already notified about a failure. @@ -4100,3 +4132,28 @@ func (l *channelLink) fail(linkErr LinkFailureError, l.failed = true l.cfg.OnChannelFailure(l.ChanID(), l.ShortChanID(), linkErr) } + +// FundingCustomBlob returns the custom funding blob of the channel that this +// link is associated with. The funding blob represents static information about +// the channel that was created at channel funding time. +func (l *channelLink) FundingCustomBlob() fn.Option[tlv.Blob] { + if l.channel == nil { + return fn.None[tlv.Blob]() + } + + if l.channel.State() == nil { + return fn.None[tlv.Blob]() + } + + return l.channel.State().CustomBlob +} + +// CommitmentCustomBlob returns the custom blob of the current local commitment +// of the channel that this link is associated with. +func (l *channelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] { + if l.channel == nil { + return fn.None[tlv.Blob]() + } + + return l.channel.LocalCommitmentBlob() +} diff --git a/htlcswitch/link_isolated_test.go b/htlcswitch/link_isolated_test.go index 89d0ef3193..5281cbed7f 100644 --- a/htlcswitch/link_isolated_test.go +++ b/htlcswitch/link_isolated_test.go @@ -1,6 +1,7 @@ package htlcswitch import ( + "context" "crypto/sha256" "testing" "time" @@ -94,7 +95,9 @@ func (l *linkTestContext) receiveHtlcAliceToBob() { func (l *linkTestContext) sendCommitSigBobToAlice(expHtlcs int) { l.t.Helper() - sigs, err := l.bobChannel.SignNextCommitment() + testQuit, testQuitFunc := context.WithCancel(context.Background()) + defer testQuitFunc() + sigs, err := l.bobChannel.SignNextCommitment(testQuit) if err != nil { l.t.Fatalf("error signing commitment: %v", err) } @@ -129,7 +132,7 @@ func (l *linkTestContext) receiveRevAndAckAliceToBob() { l.t.Fatalf("expected RevokeAndAck, got %T", msg) } - _, _, _, _, err := l.bobChannel.ReceiveRevocation(rev) + _, _, err := l.bobChannel.ReceiveRevocation(rev) if err != nil { l.t.Fatalf("bob failed receiving revocation: %v", err) } diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index f50df8cd5e..574f3a6778 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -268,9 +268,12 @@ func TestChannelLinkRevThenSig(t *testing.T) { // Restart Bob as well by calling NewLightningChannel. bobSigner := harness.bobChannel.Signer + signerMock := lnwallet.NewDefaultAuxSignerMock(t) bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner) bobChannel, err := lnwallet.NewLightningChannel( bobSigner, harness.bobChannel.State(), bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) require.NoError(t, err) err = bobPool.Start() @@ -403,9 +406,12 @@ func TestChannelLinkSigThenRev(t *testing.T) { // Restart Bob as well by calling NewLightningChannel. bobSigner := harness.bobChannel.Signer + signerMock := lnwallet.NewDefaultAuxSignerMock(t) bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner) bobChannel, err := lnwallet.NewLightningChannel( bobSigner, harness.bobChannel.State(), bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) require.NoError(t, err) err = bobPool.Start() @@ -2191,17 +2197,21 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt, return nil } + forwardPackets := func(linkQuit <-chan struct{}, _ bool, + packets ...*htlcPacket) error { + + return aliceSwitch.ForwardPackets(linkQuit, packets...) + } + // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ - FwrdingPolicy: globalPolicy, - Peer: alicePeer, - BestHeight: aliceSwitch.BestHeight, - Circuits: aliceSwitch.CircuitModifier(), - ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { - return aliceSwitch.ForwardPackets(linkQuit, packets...) - }, + FwrdingPolicy: globalPolicy, + Peer: alicePeer, + BestHeight: aliceSwitch.BestHeight, + Circuits: aliceSwitch.CircuitModifier(), + ForwardPackets: forwardPackets, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -2242,12 +2252,14 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt, return aliceSwitch.AddLink(aliceLink) } go func() { - for { - select { - case <-notifyUpdateChan: - case <-aliceLink.(*channelLink).quit: - close(doneChan) - return + if chanLink, ok := aliceLink.(*channelLink); ok { + for { + select { + case <-notifyUpdateChan: + case <-chanLink.Quit: + close(doneChan) + return + } } } }() @@ -2314,7 +2326,10 @@ func handleStateUpdate(link *channelLink, } link.HandleChannelUpdate(remoteRev) - remoteSigs, err := remoteChannel.SignNextCommitment() + ctx, done := link.WithCtxQuitNoTimeout() + defer done() + + remoteSigs, err := remoteChannel.SignNextCommitment(ctx) if err != nil { return err } @@ -2335,7 +2350,7 @@ func handleStateUpdate(link *channelLink, if !ok { return fmt.Errorf("expected RevokeAndAck got %T", msg) } - _, _, _, _, err = remoteChannel.ReceiveRevocation(revoke) + _, _, err = remoteChannel.ReceiveRevocation(revoke) if err != nil { return fmt.Errorf("unable to receive "+ "revocation: %v", err) @@ -2357,7 +2372,7 @@ func updateState(batchTick chan time.Time, link *channelLink, // Trigger update by ticking the batchTicker. select { case batchTick <- time.Now(): - case <-link.quit: + case <-link.Quit: return fmt.Errorf("link shutting down") } return handleStateUpdate(link, remoteChannel) @@ -2365,7 +2380,10 @@ func updateState(batchTick chan time.Time, link *channelLink, // The remote is triggering the state update, emulate this by // signing and sending CommitSig to the link. - remoteSigs, err := remoteChannel.SignNextCommitment() + ctx, done := link.WithCtxQuitNoTimeout() + defer done() + + remoteSigs, err := remoteChannel.SignNextCommitment(ctx) if err != nil { return err } @@ -2389,7 +2407,7 @@ func updateState(batchTick chan time.Time, link *channelLink, return fmt.Errorf("expected RevokeAndAck got %T", msg) } - _, _, _, _, err = remoteChannel.ReceiveRevocation(revoke) + _, _, err = remoteChannel.ReceiveRevocation(revoke) if err != nil { return fmt.Errorf("unable to receive "+ "revocation: %v", err) @@ -3643,7 +3661,7 @@ func TestChannelLinkTrimCircuitsRemoteCommit(t *testing.T) { rev, _, _, err := harness.bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke current commitment") - _, _, _, _, err = alice.channel.ReceiveRevocation(rev) + _, _, err = alice.channel.ReceiveRevocation(rev) require.NoError(t, err, "unable to receive revocation") // Restart Alice's link, which simulates a disconnection with the remote @@ -4861,17 +4879,21 @@ func (h *persistentLinkHarness) restartLink( return nil } + forwardPackets := func(linkQuit <-chan struct{}, _ bool, + packets ...*htlcPacket) error { + + return h.hSwitch.ForwardPackets(linkQuit, packets...) + } + // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ - FwrdingPolicy: globalPolicy, - Peer: alicePeer, - BestHeight: h.hSwitch.BestHeight, - Circuits: h.hSwitch.CircuitModifier(), - ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { - return h.hSwitch.ForwardPackets(linkQuit, packets...) - }, + FwrdingPolicy: globalPolicy, + Peer: alicePeer, + BestHeight: h.hSwitch.BestHeight, + Circuits: h.hSwitch.CircuitModifier(), + ForwardPackets: forwardPackets, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -4881,7 +4903,8 @@ func (h *persistentLinkHarness) restartLink( FetchLastChannelUpdate: mockGetChanUpdateMessage, PreimageCache: pCache, OnChannelFailure: func(lnwire.ChannelID, - lnwire.ShortChannelID, LinkFailureError) { // nolint:whitespace + lnwire.ShortChannelID, LinkFailureError) { + }, UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil @@ -4916,12 +4939,14 @@ func (h *persistentLinkHarness) restartLink( return nil, nil, err } go func() { - for { - select { - case <-notifyUpdateChan: - case <-aliceLink.(*channelLink).quit: - close(doneChan) - return + if chanLink, ok := aliceLink.(*channelLink); ok { + for { + select { + case <-notifyUpdateChan: + case <-chanLink.Quit: + close(doneChan) + return + } } } }() @@ -5836,7 +5861,7 @@ func TestChannelLinkFail(t *testing.T) { c.cfg.Peer.(*mockPeer).disconnected = true }, func(*testing.T, *Switch, *channelLink, - *lnwallet.LightningChannel) { //nolint:whitespace,lll + *lnwallet.LightningChannel) { // Should fail at startup. }, @@ -5856,7 +5881,7 @@ func TestChannelLinkFail(t *testing.T) { c.channel.State().Packager = pkg }, func(*testing.T, *Switch, *channelLink, - *lnwallet.LightningChannel) { //nolint:whitespace,lll + *lnwallet.LightningChannel) { // Should fail at startup. }, @@ -5904,7 +5929,12 @@ func TestChannelLinkFail(t *testing.T) { // Sign a commitment that will include // signature for the HTLC just sent. - sigs, err := remoteChannel.SignNextCommitment() + quitCtx, done := c.WithCtxQuitNoTimeout() + defer done() + + sigs, err := remoteChannel.SignNextCommitment( + quitCtx, + ) if err != nil { t.Fatalf("error signing commitment: %v", err) @@ -5946,7 +5976,12 @@ func TestChannelLinkFail(t *testing.T) { // Sign a commitment that will include // signature for the HTLC just sent. - sigs, err := remoteChannel.SignNextCommitment() + quitCtx, done := c.WithCtxQuitNoTimeout() + defer done() + + sigs, err := remoteChannel.SignNextCommitment( + quitCtx, + ) if err != nil { t.Fatalf("error signing commitment: %v", err) @@ -7030,7 +7065,7 @@ func TestPipelineSettle(t *testing.T) { // erroneously forwarded. If the forwardChan is closed before the last // step, then the test will fail. forwardChan := make(chan struct{}) - fwdPkts := func(c chan struct{}, _ bool, hp ...*htlcPacket) error { + fwdPkts := func(c <-chan struct{}, _ bool, hp ...*htlcPacket) error { close(forwardChan) return nil } @@ -7216,7 +7251,7 @@ func TestChannelLinkShortFailureRelay(t *testing.T) { aliceMsgs := mockPeer.sentMsgs switchChan := make(chan *htlcPacket) - coreLink.cfg.ForwardPackets = func(linkQuit chan struct{}, _ bool, + coreLink.cfg.ForwardPackets = func(linkQuit <-chan struct{}, _ bool, packets ...*htlcPacket) error { for _, p := range packets { diff --git a/htlcswitch/mailbox.go b/htlcswitch/mailbox.go index 9b82f8912e..b283825dd9 100644 --- a/htlcswitch/mailbox.go +++ b/htlcswitch/mailbox.go @@ -95,7 +95,7 @@ type mailBoxConfig struct { // forwardPackets send a varidic number of htlcPackets to the switch to // be routed. A quit channel should be provided so that the call can // properly exit during shutdown. - forwardPackets func(chan struct{}, ...*htlcPacket) error + forwardPackets func(<-chan struct{}, ...*htlcPacket) error // clock is a time source for the mailbox. clock clock.Clock @@ -804,7 +804,7 @@ type mailOrchConfig struct { // forwardPackets send a varidic number of htlcPackets to the switch to // be routed. A quit channel should be provided so that the call can // properly exit during shutdown. - forwardPackets func(chan struct{}, ...*htlcPacket) error + forwardPackets func(<-chan struct{}, ...*htlcPacket) error // clock is a time source for the generated mailboxes. clock clock.Clock diff --git a/htlcswitch/mailbox_test.go b/htlcswitch/mailbox_test.go index e48aacfcd6..aa2f1b3ee6 100644 --- a/htlcswitch/mailbox_test.go +++ b/htlcswitch/mailbox_test.go @@ -250,7 +250,7 @@ func newMailboxContext(t *testing.T, startTime time.Time, return ctx } -func (c *mailboxContext) forward(_ chan struct{}, +func (c *mailboxContext) forward(_ <-chan struct{}, pkts ...*htlcPacket) error { for _, pkt := range pkts { @@ -706,7 +706,7 @@ func TestMailOrchestrator(t *testing.T) { // First, we'll create a new instance of our orchestrator. mo := newMailOrchestrator(&mailOrchConfig{ failMailboxUpdate: failMailboxUpdate, - forwardPackets: func(_ chan struct{}, + forwardPackets: func(_ <-chan struct{}, pkts ...*htlcPacket) error { return nil diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index ed05a31655..750bdf784f 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -35,6 +35,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" + "github.com/lightningnetwork/lnd/tlv" ) func isAlias(scid lnwire.ShortChannelID) bool { @@ -950,6 +951,14 @@ func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) { // TODO(proofofkeags): Implement } +func (f *mockChannelLink) FundingCustomBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + +func (f *mockChannelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + var _ ChannelLink = (*mockChannelLink)(nil) func newDB() (*channeldb.DB, func(), error) { @@ -1005,6 +1014,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry { panic(err) } + modifierMock := &invoices.MockHtlcModifier{} registry := invoices.NewRegistry( cdb, invoices.NewInvoiceExpiryWatcher( @@ -1013,6 +1023,7 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry { ), &invoices.RegistryConfig{ FinalCltvRejectDelta: 5, + HtlcInterceptor: modifierMock, }, ) registry.Start() @@ -1038,11 +1049,12 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice( func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash, amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey models.CircuitKey, hodlChan chan<- interface{}, + wireCustomRecords lnwire.CustomRecords, payload invoices.Payload) (invoices.HtlcResolution, error) { event, err := i.registry.NotifyExitHopHtlc( - rhash, amt, expiry, currentHeight, circuitKey, hodlChan, - payload, + rhash, amt, expiry, currentHeight, circuitKey, + hodlChan, wireCustomRecords, payload, ) if err != nil { return nil, err @@ -1152,22 +1164,27 @@ type mockHTLCNotifier struct { } func (h *mockHTLCNotifier) NotifyForwardingEvent(key HtlcKey, info HtlcInfo, - eventType HtlcEventType) { //nolint:whitespace + eventType HtlcEventType) { + } func (h *mockHTLCNotifier) NotifyLinkFailEvent(key HtlcKey, info HtlcInfo, eventType HtlcEventType, linkErr *LinkError, - incoming bool) { //nolint:whitespace + incoming bool) { + } func (h *mockHTLCNotifier) NotifyForwardingFailEvent(key HtlcKey, - eventType HtlcEventType) { //nolint:whitespace + eventType HtlcEventType) { + } func (h *mockHTLCNotifier) NotifySettleEvent(key HtlcKey, - preimage lntypes.Preimage, eventType HtlcEventType) { //nolint:whitespace,lll + preimage lntypes.Preimage, eventType HtlcEventType) { + } func (h *mockHTLCNotifier) NotifyFinalHtlcEvent(key models.CircuitKey, - info channeldb.FinalHtlcInfo) { //nolint:whitespace + info channeldb.FinalHtlcInfo) { + } diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 45f4e465b6..31639dd5d1 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -94,9 +94,13 @@ type htlcPacket struct { // link. outgoingTimeout uint32 - // customRecords are user-defined records in the custom type range that - // were included in the payload. - customRecords record.CustomSet + // inOnionCustomRecords are user-defined records in the custom type + // range that were included in the onion payload. + inOnionCustomRecords record.CustomSet + + // inWireCustomRecords are custom type range TLVs that are included + // in the incoming update_add_htlc wire message. + inWireCustomRecords lnwire.CustomRecords // originalOutgoingChanID is used when sending back failure messages. // It is only used for forwarded Adds on option_scid_alias channels. diff --git a/htlcswitch/payment_result_test.go b/htlcswitch/payment_result_test.go index 99e8074e58..664197f765 100644 --- a/htlcswitch/payment_result_test.go +++ b/htlcswitch/payment_result_test.go @@ -38,7 +38,6 @@ func TestNetworkResultSerialization(t *testing.T) { ChanID: chanID, ID: 2, PaymentPreimage: preimage, - ExtraData: make([]byte, 0), } fail := &lnwire.UpdateFailHTLC{ diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index efd469f785..35eb4a6ef4 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -671,7 +671,7 @@ func (s *Switch) IsForwardedHTLC(chanID lnwire.ShortChannelID, // given to forward them through the router. The sending link's quit channel is // used to prevent deadlocks when the switch stops a link in the midst of // forwarding. -func (s *Switch) ForwardPackets(linkQuit chan struct{}, +func (s *Switch) ForwardPackets(linkQuit <-chan struct{}, packets ...*htlcPacket) error { var ( @@ -849,7 +849,7 @@ func (s *Switch) logFwdErrs(num *int, wg *sync.WaitGroup, fwdChan chan error) { // receive a shutdown requuest. This method does not wait for a response from // the htlcForwarder before returning. func (s *Switch) routeAsync(packet *htlcPacket, errChan chan error, - linkQuit chan struct{}) error { + linkQuit <-chan struct{}) error { command := &plexPacket{ pkt: packet, @@ -1911,18 +1911,8 @@ func (s *Switch) loadChannelFwdPkgs(source lnwire.ShortChannelID) ([]*channeldb. // NOTE: This should mimic the behavior processRemoteSettleFails. func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) { for _, fwdPkg := range fwdPkgs { - settleFails, err := lnwallet.PayDescsFromRemoteLogUpdates( - fwdPkg.Source, fwdPkg.Height, fwdPkg.SettleFails, - ) - if err != nil { - log.Errorf("Unable to process remote log updates: %v", - err) - continue - } - - switchPackets := make([]*htlcPacket, 0, len(settleFails)) - for i, pd := range settleFails { - + switchPackets := make([]*htlcPacket, 0, len(fwdPkg.SettleFails)) + for i, update := range fwdPkg.SettleFails { // Skip any settles or fails that have already been // acknowledged by the incoming link that originated the // forwarded Add. @@ -1930,20 +1920,18 @@ func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) { continue } - switch pd.EntryType { - + switch msg := update.UpdateMsg.(type) { // A settle for an HTLC we previously forwarded HTLC has // been received. So we'll forward the HTLC to the // switch which will handle propagating the settle to // the prior hop. - case lnwallet.Settle: + case *lnwire.UpdateFulfillHTLC: + destRef := fwdPkg.DestRef(uint16(i)) settlePacket := &htlcPacket{ outgoingChanID: fwdPkg.Source, - outgoingHTLCID: pd.ParentIndex, - destRef: pd.DestRef, - htlc: &lnwire.UpdateFulfillHTLC{ - PaymentPreimage: pd.RPreimage, - }, + outgoingHTLCID: msg.ID, + destRef: &destRef, + htlc: msg, } // Add the packet to the batch to be forwarded, and @@ -1955,7 +1943,7 @@ func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) { // received. As a result a new slot will be freed up in our // commitment state, so we'll forward this to the switch so the // backwards undo can continue. - case lnwallet.Fail: + case *lnwire.UpdateFailHTLC: // Fetch the reason the HTLC was canceled so // we can continue to propagate it. This // failure originated from another node, so @@ -1964,11 +1952,13 @@ func (s *Switch) reforwardSettleFails(fwdPkgs []*channeldb.FwdPkg) { // additional circuit information for us. failPacket := &htlcPacket{ outgoingChanID: fwdPkg.Source, - outgoingHTLCID: pd.ParentIndex, - destRef: pd.DestRef, - htlc: &lnwire.UpdateFailHTLC{ - Reason: lnwire.OpaqueReason(pd.FailReason), + outgoingHTLCID: msg.ID, + destRef: &channeldb.SettleFailRef{ + Source: fwdPkg.Source, + Height: fwdPkg.Height, + Index: uint16(i), }, + htlc: msg, } // Add the packet to the batch to be forwarded, and @@ -2090,6 +2080,26 @@ func (s *Switch) addLiveLink(link ChannelLink) { } s.interfaceIndex[peerPub][link.ChanID()] = link + s.updateLinkAliases(link) +} + +// UpdateLinkAliases is the externally exposed wrapper for updating link +// aliases. It acquires the indexMtx and calls the internal method. +func (s *Switch) UpdateLinkAliases(link ChannelLink) { + s.indexMtx.Lock() + defer s.indexMtx.Unlock() + + s.updateLinkAliases(link) +} + +// updateLinkAliases updates the aliases for a given link. This will cause the +// htlcswitch to consult the alias manager on the up to date values of its +// alias maps. +// +// NOTE: this MUST be called with the indexMtx held. +func (s *Switch) updateLinkAliases(link ChannelLink) { + linkScid := link.ShortChanID() + aliases := link.getAliases() if link.isZeroConf() { if link.zeroConfConfirmed() { @@ -2114,6 +2124,21 @@ func (s *Switch) addLiveLink(link ChannelLink) { s.baseIndex[alias] = linkScid } } else if link.negotiatedAliasFeature() { + // First, we flush any alias mappings for this link's scid + // before we populate the map again, in order to get rid of old + // values that no longer exist. + for alias, real := range s.aliasToReal { + if real == linkScid { + delete(s.aliasToReal, alias) + } + } + + for alias, real := range s.baseIndex { + if real == linkScid { + delete(s.baseIndex, alias) + } + } + // The link's SCID is the confirmed SCID for non-zero-conf // option-scid-alias feature bit channels. for _, alias := range aliases { diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index a71577ef2b..67aaa4a957 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -351,8 +351,11 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, ) alicePool := lnwallet.NewSigPool(runtime.NumCPU(), aliceSigner) + signerMock := lnwallet.NewDefaultAuxSignerMock(t) channelAlice, err := lnwallet.NewLightningChannel( aliceSigner, aliceChannelState, alicePool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, nil, err @@ -362,6 +365,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner) channelBob, err := lnwallet.NewLightningChannel( bobSigner, bobChannelState, bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, nil, err @@ -423,6 +428,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, newAliceChannel, err := lnwallet.NewLightningChannel( aliceSigner, aliceStoredChannel, alicePool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, errors.Errorf("unable to create new channel: %v", @@ -469,6 +476,8 @@ func createTestChannel(t *testing.T, alicePrivKey, bobPrivKey []byte, newBobChannel, err := lnwallet.NewLightningChannel( bobSigner, bobStoredChannel, bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(signerMock), ) if err != nil { return nil, errors.Errorf("unable to create new channel: %v", @@ -1133,15 +1142,19 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, return nil } + forwardPackets := func(linkQuit <-chan struct{}, _ bool, + packets ...*htlcPacket) error { + + return server.htlcSwitch.ForwardPackets(linkQuit, packets...) + } + link := NewChannelLink( ChannelLinkConfig{ - BestHeight: server.htlcSwitch.BestHeight, - FwrdingPolicy: h.globalPolicy, - Peer: peer, - Circuits: server.htlcSwitch.CircuitModifier(), - ForwardPackets: func(linkQuit chan struct{}, _ bool, packets ...*htlcPacket) error { - return server.htlcSwitch.ForwardPackets(linkQuit, packets...) - }, + BestHeight: server.htlcSwitch.BestHeight, + FwrdingPolicy: h.globalPolicy, + Peer: peer, + Circuits: server.htlcSwitch.CircuitModifier(), + ForwardPackets: forwardPackets, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { @@ -1182,12 +1195,14 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, } go func() { - for { - select { - case <-notifyUpdateChan: - case <-link.(*channelLink).quit: - close(doneChan) - return + if chanLink, ok := link.(*channelLink); ok { + for { + select { + case <-notifyUpdateChan: + case <-chanLink.Quit: + close(doneChan) + return + } } } }() diff --git a/input/input.go b/input/input.go index aef524a4c9..6835f82c8e 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,13 @@ 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] + + // Preimage returns the preimage for the input if it is an HTLC input. + Preimage() fn.Option[lntypes.Preimage] } // TxInfo describes properties of a parent tx that are relevant for CPFP. @@ -106,6 +115,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 +169,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 +209,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 +231,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 +245,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 @@ -243,6 +288,11 @@ func (bi *BaseInput) CraftInputScript(signer Signer, txn *wire.MsgTx, return witnessFunc(txn, hashCache, txinIdx) } +// Preimage returns the preimage for the input if it is an HTLC input. +func (bi *BaseInput) Preimage() fn.Option[lntypes.Preimage] { + return fn.None[lntypes.Preimage]() +} + // HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input // is expected to reside on the commitment tx of the remote party and should // not be a second level tx output. @@ -256,16 +306,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 +324,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, } } @@ -314,7 +365,6 @@ func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx, } desc.SignMethod = TaprootScriptSpendSignMethod - witness, err = SenderHTLCScriptTaprootRedeem( signer, &desc, txn, h.preimage, nil, nil, ) @@ -332,6 +382,15 @@ func (h *HtlcSucceedInput) CraftInputScript(signer Signer, txn *wire.MsgTx, }, nil } +// Preimage returns the preimage for the input if it is an HTLC input. +func (h *HtlcSucceedInput) Preimage() fn.Option[lntypes.Preimage] { + if len(h.preimage) == 0 { + return fn.None[lntypes.Preimage]() + } + + return fn.Some(lntypes.Preimage(h.preimage)) +} + // HtlcSecondLevelAnchorInput is an input type used to spend HTLC outputs // using a re-signed second level transaction, either via the timeout or success // paths. @@ -348,6 +407,8 @@ type HtlcSecondLevelAnchorInput struct { hashCache *txscript.TxSigHashes, prevOutputFetcher txscript.PrevOutputFetcher, txinIdx int) (wire.TxWitness, error) + + preimage []byte } // RequiredTxOut returns the tx out needed to be present on the sweep tx for @@ -384,11 +445,21 @@ func (i *HtlcSecondLevelAnchorInput) CraftInputScript(signer Signer, }, nil } +// Preimage returns the preimage for the input if it is an HTLC input. +func (i *HtlcSecondLevelAnchorInput) Preimage() fn.Option[lntypes.Preimage] { + if len(i.preimage) == 0 { + return fn.None[lntypes.Preimage]() + } + + return fn.Some(lntypes.Preimage(i.preimage)) +} + // MakeHtlcSecondLevelTimeoutAnchorInput creates an input allowing the sweeper // 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 +479,15 @@ func MakeHtlcSecondLevelTimeoutAnchorInput(signedTx *wire.MsgTx, ) } + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + HtlcOfferedTimeoutSecondLevelInputConfirmed, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 + return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: HtlcOfferedTimeoutSecondLevelInputConfirmed, - signDesc: signDetails.SignDesc, - heightHint: heightHint, - - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, } @@ -429,7 +499,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 +523,15 @@ func MakeHtlcSecondLevelTimeoutTaprootInput(signedTx *wire.MsgTx, ) } + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + TaprootHtlcLocalOfferedTimeout, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 + return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: TaprootHtlcLocalOfferedTimeout, - signDesc: signDetails.SignDesc, - heightHint: heightHint, - - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, } @@ -473,7 +542,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,19 +561,18 @@ 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, + preimage: preimage[:], } } @@ -513,7 +581,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,18 +605,18 @@ func MakeHtlcSecondLevelSuccessTaprootInput(signedTx *wire.MsgTx, ) } + input := MakeBaseInput( + &signedTx.TxIn[0].PreviousOutPoint, + TaprootHtlcAcceptedLocalSuccess, + &signDetails.SignDesc, heightHint, nil, opts..., + ) + input.blockToMaturity = 1 + return HtlcSecondLevelAnchorInput{ - inputKit: inputKit{ - outpoint: signedTx.TxIn[0].PreviousOutPoint, - witnessType: TaprootHtlcAcceptedLocalSuccess, - signDesc: signDetails.SignDesc, - heightHint: heightHint, - - // CSV delay is always 1 for these inputs. - blockToMaturity: 1, - }, + inputKit: input.inputKit, SignedTx: signedTx, createWitness: createWitness, + preimage: preimage[:], } } diff --git a/input/mocks.go b/input/mocks.go index 2f38400d81..c2af637eda 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,28 @@ 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]) +} + +func (m *MockInput) Preimage() fn.Option[lntypes.Preimage] { + args := m.Called() + + info := args.Get(0) + if info == nil { + return fn.None[lntypes.Preimage]() + } + + return info.(fn.Option[lntypes.Preimage]) +} + // MockWitnessType implements the `WitnessType` interface and is used by other // packages for mock testing. type MockWitnessType struct { diff --git a/input/script_desc.go b/input/script_desc.go index a32ae66316..17b58b7219 100644 --- a/input/script_desc.go +++ b/input/script_desc.go @@ -33,17 +33,17 @@ const ( ScriptPathDelay ) -// ScriptDesciptor is an interface that abstracts over the various ways a +// ScriptDescriptor is an interface that abstracts over the various ways a // pkScript can be spent from an output. This supports both normal p2wsh -// (witness script, etc), and also tapscript paths which have distinct +// (witness script, etc.), and also tapscript paths which have distinct // tapscript leaves. type ScriptDescriptor interface { // PkScript is the public key script that commits to the final // contract. PkScript() []byte - // WitnessScript returns the witness script that we'll use when signing - // for the remote party, and also verifying signatures on our + // WitnessScriptToSign returns the witness script that we'll use when + // signing for the remote party, and also verifying signatures on our // transactions. As an example, when we create an outgoing HTLC for the // remote party, we want to sign their success path. // @@ -73,6 +73,9 @@ type TapscriptDescriptor interface { // TapScriptTree returns the underlying tapscript tree. TapScriptTree() *txscript.IndexedTapScriptTree + + // Tree returns the underlying ScriptTree. + Tree() ScriptTree } // ScriptTree holds the contents needed to spend a script within a tapscript diff --git a/input/script_utils.go b/input/script_utils.go index 104c242510..91ca55292f 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -11,8 +11,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnutils" "golang.org/x/crypto/ripemd160" @@ -199,26 +201,30 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro } // GenTaprootFundingScript constructs the taproot-native funding output that -// uses musig2 to create a single aggregated key to anchor the channel. +// uses MuSig2 to create a single aggregated key to anchor the channel. func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey, - amt int64) ([]byte, *wire.TxOut, error) { + amt int64, tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, + *wire.TxOut, error) { + + muSig2Opt := musig2.WithBIP86KeyTweak() + tapscriptRoot.WhenSome(func(scriptRoot chainhash.Hash) { + muSig2Opt = musig2.WithTaprootKeyTweak(scriptRoot[:]) + }) // Similar to the existing p2wsh funding script, we'll always make sure // we sort the keys before any major operations. In order to ensure // that there's no other way this output can be spent, we'll use a BIP - // 86 tweak here during aggregation. - // - // TODO(roasbeef): revisit if BIP 86 is needed here? + // 86 tweak here during aggregation, unless the user has explicitly + // specified a tapscript root. combinedKey, _, _, err := musig2.AggregateKeys( - []*btcec.PublicKey{aPub, bPub}, true, - musig2.WithBIP86KeyTweak(), + []*btcec.PublicKey{aPub, bPub}, true, muSig2Opt, ) if err != nil { return nil, nil, fmt.Errorf("unable to combine keys: %w", err) } // Now that we have the combined key, we can create a taproot pkScript - // from this, and then make the txout given the amount. + // from this, and then make the txOut given the amount. pkScript, err := PayToTaprootScript(combinedKey.FinalKey) if err != nil { return nil, nil, fmt.Errorf("unable to make taproot "+ @@ -228,7 +234,7 @@ func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey, txOut := wire.NewTxOut(amt, pkScript) // For the "witness program" we just return the raw pkScript since the - // output we create can _only_ be spent with a musig2 signature. + // output we create can _only_ be spent with a MuSig2 signature. return pkScript, txOut, nil } @@ -640,6 +646,13 @@ type HtlcScriptTree struct { // TimeoutTapLeaf is the tapleaf for the timeout path. TimeoutTapLeaf txscript.TapLeaf + // AuxLeaf is an auxiliary leaf that can be used to extend the base + // HTLC script tree with new spend paths, or just as extra commitment + // space. When present, this leaf will always be in the right-most area + // of the tapscript tree. + AuxLeaf AuxTapLeaf + + // htlcType is the type of HTLC script this is. htlcType htlcType } @@ -689,8 +702,8 @@ func (h *HtlcScriptTree) WitnessScriptForPath(path ScriptPath) ([]byte, error) { // CtrlBlockForPath returns the control block for the given spending path. For // script types that don't have a control block, nil is returned. -func (h *HtlcScriptTree) CtrlBlockForPath(path ScriptPath, -) (*txscript.ControlBlock, error) { +func (h *HtlcScriptTree) CtrlBlockForPath( + path ScriptPath) (*txscript.ControlBlock, error) { switch path { case ScriptPathSuccess: @@ -708,6 +721,11 @@ func (h *HtlcScriptTree) CtrlBlockForPath(path ScriptPath, } } +// Tree returns the underlying ScriptTree of the HtlcScriptTree. +func (h *HtlcScriptTree) Tree() ScriptTree { + return h.ScriptTree +} + // A compile time check to ensure HtlcScriptTree implements the // TapscriptMultiplexer interface. var _ TapscriptDescriptor = (*HtlcScriptTree)(nil) @@ -715,8 +733,8 @@ var _ TapscriptDescriptor = (*HtlcScriptTree)(nil) // senderHtlcTapScriptTree builds the tapscript tree which is used to anchor // the HTLC key for HTLCs on the sender's commitment. func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, - revokeKey *btcec.PublicKey, payHash []byte, - hType htlcType) (*HtlcScriptTree, error) { + revokeKey *btcec.PublicKey, payHash []byte, hType htlcType, + auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. @@ -733,11 +751,14 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, return nil, err } + tapLeaves := []txscript.TapLeaf{successTapLeaf, timeoutTapLeaf} + auxLeaf.WhenSome(func(l txscript.TapLeaf) { + tapLeaves = append(tapLeaves, l) + }) + // With the two leaves obtained, we'll now make the tapscript tree, // then obtain the root from that - tapscriptTree := txscript.AssembleTaprootScriptTree( - successTapLeaf, timeoutTapLeaf, - ) + tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...) tapScriptRoot := tapscriptTree.RootNode.TapHash() @@ -756,6 +777,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, }, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, + AuxLeaf: auxLeaf, htlcType: hType, }, nil } @@ -790,7 +812,8 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // unilaterally spend the created output. func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, revokeKey *btcec.PublicKey, payHash []byte, - whoseCommit lntypes.ChannelParty) (*HtlcScriptTree, error) { + whoseCommit lntypes.ChannelParty, auxLeaf AuxTapLeaf) (*HtlcScriptTree, + error) { var hType htlcType if whoseCommit.IsLocal() { @@ -803,8 +826,8 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, // tree that includes the top level output script, as well as the two // tap leaf paths. return senderHtlcTapScriptTree( - senderHtlcKey, receiverHtlcKey, revokeKey, payHash, - hType, + senderHtlcKey, receiverHtlcKey, revokeKey, payHash, hType, + auxLeaf, ) } @@ -1274,8 +1297,8 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, // receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor // the HTLC key for HTLCs on the receiver's commitment. func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, - revokeKey *btcec.PublicKey, payHash []byte, - cltvExpiry uint32, hType htlcType) (*HtlcScriptTree, error) { + revokeKey *btcec.PublicKey, payHash []byte, cltvExpiry uint32, + hType htlcType, auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { // First, we'll obtain the tap leaves for both the success and timeout // path. @@ -1292,11 +1315,14 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, return nil, err } + tapLeaves := []txscript.TapLeaf{timeoutTapLeaf, successTapLeaf} + auxLeaf.WhenSome(func(l txscript.TapLeaf) { + tapLeaves = append(tapLeaves, l) + }) + // With the two leaves obtained, we'll now make the tapscript tree, // then obtain the root from that - tapscriptTree := txscript.AssembleTaprootScriptTree( - timeoutTapLeaf, successTapLeaf, - ) + tapscriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...) tapScriptRoot := tapscriptTree.RootNode.TapHash() @@ -1315,6 +1341,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, }, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, + AuxLeaf: auxLeaf, htlcType: hType, }, nil } @@ -1350,7 +1377,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, func ReceiverHTLCScriptTaproot(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, payHash []byte, whoseCommit lntypes.ChannelParty, -) (*HtlcScriptTree, error) { + auxLeaf AuxTapLeaf) (*HtlcScriptTree, error) { var hType htlcType if whoseCommit.IsLocal() { @@ -1364,7 +1391,7 @@ func ReceiverHTLCScriptTaproot(cltvExpiry uint32, // tap leaf paths. return receiverHtlcTapScriptTree( senderHtlcKey, receiverHtlcKey, revocationKey, payHash, - cltvExpiry, hType, + cltvExpiry, hType, auxLeaf, ) } @@ -1593,9 +1620,9 @@ func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, } // SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to -// generate the taptweak to create the final output and also control block. -func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, - csvDelay uint32) (*txscript.IndexedTapScriptTree, error) { +// generate the tap tweak to create the final output and also control block. +func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, csvDelay uint32, + auxLeaf AuxTapLeaf) (*txscript.IndexedTapScriptTree, error) { // First grab the second level leaf script we need to create the top // level output. @@ -1604,9 +1631,14 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, return nil, err } + tapLeaves := []txscript.TapLeaf{secondLevelTapLeaf} + auxLeaf.WhenSome(func(l txscript.TapLeaf) { + tapLeaves = append(tapLeaves, l) + }) + // Now that we have the sole second level script, we can create the // tapscript tree that commits to both the leaves. - return txscript.AssembleTaprootScriptTree(secondLevelTapLeaf), nil + return txscript.AssembleTaprootScriptTree(tapLeaves...), nil } // TaprootSecondLevelHtlcScript is the uniform script that's used as the output @@ -1626,12 +1658,12 @@ func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, // // The keyspend path require knowledge of the top level revocation private key. func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey, - csvDelay uint32) (*btcec.PublicKey, error) { + csvDelay uint32, auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) { // First, we'll make the tapscript tree that commits to the redemption // path. tapScriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey, csvDelay, + delayKey, csvDelay, auxLeaf, ) if err != nil { return nil, err @@ -1656,17 +1688,21 @@ type SecondLevelScriptTree struct { // SuccessTapLeaf is the tapleaf for the redemption path. SuccessTapLeaf txscript.TapLeaf + + // AuxLeaf is an optional leaf that can be used to extend the script + // tree. + AuxLeaf AuxTapLeaf } // TaprootSecondLevelScriptTree constructs the tapscript tree used to spend the // second level HTLC output. func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey, - csvDelay uint32) (*SecondLevelScriptTree, error) { + csvDelay uint32, auxLeaf AuxTapLeaf) (*SecondLevelScriptTree, error) { // First, we'll make the tapscript tree that commits to the redemption // path. tapScriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey, csvDelay, + delayKey, csvDelay, auxLeaf, ) if err != nil { return nil, err @@ -1687,12 +1723,13 @@ func TaprootSecondLevelScriptTree(revokeKey, delayKey *btcec.PublicKey, InternalKey: revokeKey, }, SuccessTapLeaf: tapScriptTree.LeafMerkleProofs[0].TapLeaf, + AuxLeaf: auxLeaf, }, nil } -// WitnessScript returns the witness script that we'll use when signing for the -// remote party, and also verifying signatures on our transactions. As an -// example, when we create an outgoing HTLC for the remote party, we want to +// WitnessScriptToSign returns the witness script that we'll use when signing +// for the remote party, and also verifying signatures on our transactions. As +// an example, when we create an outgoing HTLC for the remote party, we want to // sign their success path. func (s *SecondLevelScriptTree) WitnessScriptToSign() []byte { return s.SuccessTapLeaf.Script @@ -1700,8 +1737,8 @@ func (s *SecondLevelScriptTree) WitnessScriptToSign() []byte { // WitnessScriptForPath returns the witness script for the given spending path. // An error is returned if the path is unknown. -func (s *SecondLevelScriptTree) WitnessScriptForPath(path ScriptPath, -) ([]byte, error) { +func (s *SecondLevelScriptTree) WitnessScriptForPath( + path ScriptPath) ([]byte, error) { switch path { case ScriptPathDelay: @@ -1716,8 +1753,8 @@ func (s *SecondLevelScriptTree) WitnessScriptForPath(path ScriptPath, // CtrlBlockForPath returns the control block for the given spending path. For // script types that don't have a control block, nil is returned. -func (s *SecondLevelScriptTree) CtrlBlockForPath(path ScriptPath, -) (*txscript.ControlBlock, error) { +func (s *SecondLevelScriptTree) CtrlBlockForPath( + path ScriptPath) (*txscript.ControlBlock, error) { switch path { case ScriptPathDelay: @@ -1733,6 +1770,11 @@ func (s *SecondLevelScriptTree) CtrlBlockForPath(path ScriptPath, } } +// Tree returns the underlying ScriptTree of the SecondLevelScriptTree. +func (s *SecondLevelScriptTree) Tree() ScriptTree { + return s.ScriptTree +} + // A compile time check to ensure SecondLevelScriptTree implements the // TapscriptDescriptor interface. var _ TapscriptDescriptor = (*SecondLevelScriptTree)(nil) @@ -2063,15 +2105,21 @@ type CommitScriptTree struct { // RevocationLeaf is the leaf used to spend the output with the // revocation key signature. RevocationLeaf txscript.TapLeaf + + // AuxLeaf is an auxiliary leaf that can be used to extend the base + // commitment script tree with new spend paths, or just as extra + // commitment space. When present, this leaf will always be in the + // left-most or right-most area of the tapscript tree. + AuxLeaf AuxTapLeaf } // A compile time check to ensure CommitScriptTree implements the // TapscriptDescriptor interface. var _ TapscriptDescriptor = (*CommitScriptTree)(nil) -// WitnessScript returns the witness script that we'll use when signing for the -// remote party, and also verifying signatures on our transactions. As an -// example, when we create an outgoing HTLC for the remote party, we want to +// WitnessScriptToSign returns the witness script that we'll use when signing +// for the remote party, and also verifying signatures on our transactions. As +// an example, when we create an outgoing HTLC for the remote party, we want to // sign their success path. func (c *CommitScriptTree) WitnessScriptToSign() []byte { // TODO(roasbeef): abstraction leak here? always dependent @@ -2080,8 +2128,8 @@ func (c *CommitScriptTree) WitnessScriptToSign() []byte { // WitnessScriptForPath returns the witness script for the given spending path. // An error is returned if the path is unknown. -func (c *CommitScriptTree) WitnessScriptForPath(path ScriptPath, -) ([]byte, error) { +func (c *CommitScriptTree) WitnessScriptForPath( + path ScriptPath) ([]byte, error) { switch path { // For the commitment output, the delay and success path are the same, @@ -2099,8 +2147,8 @@ func (c *CommitScriptTree) WitnessScriptForPath(path ScriptPath, // CtrlBlockForPath returns the control block for the given spending path. For // script types that don't have a control block, nil is returned. -func (c *CommitScriptTree) CtrlBlockForPath(path ScriptPath, -) (*txscript.ControlBlock, error) { +func (c *CommitScriptTree) CtrlBlockForPath( + path ScriptPath) (*txscript.ControlBlock, error) { switch path { case ScriptPathDelay: @@ -2120,10 +2168,16 @@ func (c *CommitScriptTree) CtrlBlockForPath(path ScriptPath, } } +// Tree returns the underlying ScriptTree of the CommitScriptTree. +func (c *CommitScriptTree) Tree() ScriptTree { + return c.ScriptTree +} + // NewLocalCommitScriptTree returns a new CommitScript tree that can be used to // create and spend the commitment output for the local party. -func NewLocalCommitScriptTree(csvTimeout uint32, - selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) { +func NewLocalCommitScriptTree(csvTimeout uint32, selfKey, + revokeKey *btcec.PublicKey, auxLeaf AuxTapLeaf) (*CommitScriptTree, + error) { // First, we'll need to construct the tapLeaf that'll be our delay CSV // clause. @@ -2143,9 +2197,13 @@ func NewLocalCommitScriptTree(csvTimeout uint32, // the two leaves, and then obtain a root from that. delayTapLeaf := txscript.NewBaseTapLeaf(delayScript) revokeTapLeaf := txscript.NewBaseTapLeaf(revokeScript) - tapScriptTree := txscript.AssembleTaprootScriptTree( - delayTapLeaf, revokeTapLeaf, - ) + + tapLeaves := []txscript.TapLeaf{delayTapLeaf, revokeTapLeaf} + auxLeaf.WhenSome(func(l txscript.TapLeaf) { + tapLeaves = append(tapLeaves, l) + }) + + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...) tapScriptRoot := tapScriptTree.RootNode.TapHash() // Now that we have our root, we can arrive at the final output script @@ -2163,6 +2221,7 @@ func NewLocalCommitScriptTree(csvTimeout uint32, }, SettleLeaf: delayTapLeaf, RevocationLeaf: revokeTapLeaf, + AuxLeaf: auxLeaf, }, nil } @@ -2232,7 +2291,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { commitScriptTree, err := NewLocalCommitScriptTree( - csvTimeout, selfKey, revokeKey, + csvTimeout, selfKey, revokeKey, NoneTapLeaf(), ) if err != nil { return nil, err @@ -2241,7 +2300,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, return commitScriptTree.TaprootKey, nil } -// MakeTaprootSCtrlBlock takes a leaf script, the internal key (usually the +// MakeTaprootCtrlBlock takes a leaf script, the internal key (usually the // revoke key), and a script tree and creates a valid control block for a spend // of the leaf. func MakeTaprootCtrlBlock(leafScript []byte, internalKey *btcec.PublicKey, @@ -2296,9 +2355,6 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript witnessStack[2] = ctrlBlockBytes - if err != nil { - return nil, err - } return witnessStack, nil } @@ -2561,7 +2617,7 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { // NewRemoteCommitScriptTree constructs a new script tree for the remote party // to sweep their funds after a hard coded 1 block delay. func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, -) (*CommitScriptTree, error) { + auxLeaf AuxTapLeaf) (*CommitScriptTree, error) { // First, construct the remote party's tapscript they'll use to sweep // their outputs. @@ -2577,10 +2633,16 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, return nil, err } + tapLeaf := txscript.NewBaseTapLeaf(remoteScript) + + tapLeaves := []txscript.TapLeaf{tapLeaf} + auxLeaf.WhenSome(func(l txscript.TapLeaf) { + tapLeaves = append(tapLeaves, l) + }) + // With this script constructed, we'll map that into a tapLeaf, then // make a new tapscript root from that. - tapLeaf := txscript.NewBaseTapLeaf(remoteScript) - tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaves...) tapScriptRoot := tapScriptTree.RootNode.TapHash() // Now that we have our root, we can arrive at the final output script @@ -2597,6 +2659,7 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, InternalKey: &TaprootNUMSKey, }, SettleLeaf: tapLeaf, + AuxLeaf: auxLeaf, }, nil } @@ -2613,9 +2676,9 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey, // OP_CHECKSIG // 1 OP_CHECKSEQUENCEVERIFY OP_DROP func TaprootCommitScriptToRemote(remoteKey *btcec.PublicKey, -) (*btcec.PublicKey, error) { + auxLeaf AuxTapLeaf) (*btcec.PublicKey, error) { - commitScriptTree, err := NewRemoteCommitScriptTree(remoteKey) + commitScriptTree, err := NewRemoteCommitScriptTree(remoteKey, auxLeaf) if err != nil { return nil, err } @@ -2773,8 +2836,8 @@ type AnchorScriptTree struct { // NewAnchorScriptTree makes a new script tree for an anchor output with the // passed anchor key. -func NewAnchorScriptTree(anchorKey *btcec.PublicKey, -) (*AnchorScriptTree, error) { +func NewAnchorScriptTree( + anchorKey *btcec.PublicKey) (*AnchorScriptTree, error) { // The main script used is just a OP_16 CSV (anyone can sweep after 16 // blocks). @@ -2810,9 +2873,9 @@ func NewAnchorScriptTree(anchorKey *btcec.PublicKey, }, nil } -// WitnessScript returns the witness script that we'll use when signing for the -// remote party, and also verifying signatures on our transactions. As an -// example, when we create an outgoing HTLC for the remote party, we want to +// WitnessScriptToSign returns the witness script that we'll use when signing +// for the remote party, and also verifying signatures on our transactions. As +// an example, when we create an outgoing HTLC for the remote party, we want to // sign their success path. func (a *AnchorScriptTree) WitnessScriptToSign() []byte { return a.SweepLeaf.Script @@ -2820,8 +2883,8 @@ func (a *AnchorScriptTree) WitnessScriptToSign() []byte { // WitnessScriptForPath returns the witness script for the given spending path. // An error is returned if the path is unknown. -func (a *AnchorScriptTree) WitnessScriptForPath(path ScriptPath, -) ([]byte, error) { +func (a *AnchorScriptTree) WitnessScriptForPath( + path ScriptPath) ([]byte, error) { switch path { case ScriptPathDelay: @@ -2836,8 +2899,8 @@ func (a *AnchorScriptTree) WitnessScriptForPath(path ScriptPath, // CtrlBlockForPath returns the control block for the given spending path. For // script types that don't have a control block, nil is returned. -func (a *AnchorScriptTree) CtrlBlockForPath(path ScriptPath, -) (*txscript.ControlBlock, error) { +func (a *AnchorScriptTree) CtrlBlockForPath( + path ScriptPath) (*txscript.ControlBlock, error) { switch path { case ScriptPathDelay: @@ -2853,6 +2916,11 @@ func (a *AnchorScriptTree) CtrlBlockForPath(path ScriptPath, } } +// Tree returns the underlying ScriptTree of the AnchorScriptTree. +func (a *AnchorScriptTree) Tree() ScriptTree { + return a.ScriptTree +} + // A compile time check to ensure AnchorScriptTree implements the // TapscriptDescriptor interface. var _ TapscriptDescriptor = (*AnchorScriptTree)(nil) diff --git a/input/size_test.go b/input/size_test.go index daa7053ccf..33f9ff5397 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -853,7 +853,7 @@ var witnessSizeTests = []witnessSizeTest{ signer := &dummySigner{} commitScriptTree, err := input.NewLocalCommitScriptTree( testCSVDelay, testKey.PubKey(), - testKey.PubKey(), + testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) @@ -887,7 +887,7 @@ var witnessSizeTests = []witnessSizeTest{ signer := &dummySigner{} commitScriptTree, err := input.NewLocalCommitScriptTree( testCSVDelay, testKey.PubKey(), - testKey.PubKey(), + testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) @@ -921,7 +921,7 @@ var witnessSizeTests = []witnessSizeTest{ signer := &dummySigner{} //nolint:lll commitScriptTree, err := input.NewRemoteCommitScriptTree( - testKey.PubKey(), + testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) @@ -988,6 +988,7 @@ var witnessSizeTests = []witnessSizeTest{ scriptTree, err := input.SecondLevelHtlcTapscriptTree( testKey.PubKey(), testCSVDelay, + input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1027,6 +1028,7 @@ var witnessSizeTests = []witnessSizeTest{ scriptTree, err := input.SecondLevelHtlcTapscriptTree( testKey.PubKey(), testCSVDelay, + input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1075,6 +1077,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, + input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1116,7 +1119,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], lntypes.Remote, + payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1158,7 +1161,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], lntypes.Remote, + payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1205,6 +1208,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, + input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1265,6 +1269,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, + input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1310,7 +1315,7 @@ var witnessSizeTests = []witnessSizeTest{ htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], lntypes.Remote, + payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1383,7 +1388,7 @@ func genTimeoutTx(t *testing.T, // Create the unsigned timeout tx. timeoutTx, err := lnwallet.CreateHtlcTimeoutTx( chanType, false, testOutPoint, testAmt, testCLTVExpiry, - testCSVDelay, 0, testPubkey, testPubkey, + testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1396,7 +1401,7 @@ func genTimeoutTx(t *testing.T, if chanType.IsTaproot() { tapscriptTree, err = input.SenderHTLCScriptTaproot( testPubkey, testPubkey, testPubkey, testHash160, - lntypes.Remote, + lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1452,7 +1457,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx { // Create the unsigned success tx. successTx, err := lnwallet.CreateHtlcSuccessTx( chanType, false, testOutPoint, testAmt, testCSVDelay, 0, - testPubkey, testPubkey, + testPubkey, testPubkey, input.NoneTapLeaf(), ) require.NoError(t, err) @@ -1465,7 +1470,7 @@ func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx { if chanType.IsTaproot() { tapscriptTree, err = input.ReceiverHTLCScriptTaproot( testCLTVExpiry, testPubkey, testPubkey, testPubkey, - testHash160, lntypes.Remote, + testHash160, lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) diff --git a/input/taproot.go b/input/taproot.go index 34cdb974d5..2ca6e97236 100644 --- a/input/taproot.go +++ b/input/taproot.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightningnetwork/lnd/fn" ) const ( @@ -21,6 +22,33 @@ const ( PubKeyFormatCompressedOdd byte = 0x03 ) +// AuxTapLeaf is a type alias for an optional tapscript leaf that may be added +// to the tapscript tree of HTLC and commitment outputs. +type AuxTapLeaf = fn.Option[txscript.TapLeaf] + +// NoneTapLeaf returns an empty optional tapscript leaf. +func NoneTapLeaf() AuxTapLeaf { + return fn.None[txscript.TapLeaf]() +} + +// HtlcIndex represents the monotonically increasing counter that is used to +// identify HTLCs created by a peer. +type HtlcIndex = uint64 + +// HtlcAuxLeaf is a type that represents an auxiliary leaf for an HTLC output. +// An HTLC may have up to two aux leaves: one for the output on the commitment +// transaction, and one for the second level HTLC. +type HtlcAuxLeaf struct { + AuxTapLeaf + + // SecondLevelLeaf is the auxiliary leaf for the second level HTLC + // success or timeout transaction. + SecondLevelLeaf AuxTapLeaf +} + +// HtlcAuxLeaves is a type alias for a map of optional tapscript leaves. +type HtlcAuxLeaves = map[HtlcIndex]HtlcAuxLeaf + // NewTxSigHashesV0Only returns a new txscript.TxSigHashes instance that will // only calculate the sighash midstate values for segwit v0 inputs and can // therefore never be used for transactions that want to spend segwit v1 diff --git a/input/taproot_test.go b/input/taproot_test.go index 434be2dfdb..a1259be196 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -1,13 +1,16 @@ package input import ( + "bytes" "crypto/rand" + "fmt" "testing" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "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/stretchr/testify/require" @@ -31,7 +34,9 @@ type testSenderHtlcScriptTree struct { htlcAmt int64 } -func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree { +func newTestSenderHtlcScriptTree(t *testing.T, + auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree { + var preImage lntypes.Preimage _, err := rand.Read(preImage[:]) require.NoError(t, err) @@ -48,7 +53,7 @@ func newTestSenderHtlcScriptTree(t *testing.T) *testSenderHtlcScriptTree { payHash := preImage.Hash() htlcScriptTree, err := SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), - payHash[:], lntypes.Remote, + payHash[:], lntypes.Remote, auxLeaf, ) require.NoError(t, err) @@ -207,13 +212,9 @@ func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType, } } -// TestTaprootSenderHtlcSpend tests that all the positive and negative paths -// for the sender HTLC tapscript tree work as expected. -func TestTaprootSenderHtlcSpend(t *testing.T) { - t.Parallel() - +func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) { // First, create a new test script tree. - htlcScriptTree := newTestSenderHtlcScriptTree(t) + htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf) spendTx := wire.NewMsgTx(2) spendTx.AddTxIn(&wire.TxIn{}) @@ -432,6 +433,26 @@ func TestTaprootSenderHtlcSpend(t *testing.T) { } } +// TestTaprootSenderHtlcSpend tests that all the positive and negative paths +// for the sender HTLC tapscript tree work as expected. +func TestTaprootSenderHtlcSpend(t *testing.T) { + t.Parallel() + + for _, hasAuxLeaf := range []bool{true, false} { + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } + + testTaprootSenderHtlcSpend(t, auxLeaf) + }) + } +} + type testReceiverHtlcScriptTree struct { preImage lntypes.Preimage @@ -452,7 +473,9 @@ type testReceiverHtlcScriptTree struct { lockTime int32 } -func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree { +func newTestReceiverHtlcScriptTree(t *testing.T, + auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree { + var preImage lntypes.Preimage _, err := rand.Read(preImage[:]) require.NoError(t, err) @@ -471,7 +494,7 @@ func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree { payHash := preImage.Hash() htlcScriptTree, err := ReceiverHTLCScriptTaproot( cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(), - revokeKey.PubKey(), payHash[:], lntypes.Remote, + revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf, ) require.NoError(t, err) @@ -629,15 +652,11 @@ func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType, } } -// TestTaprootReceiverHtlcSpend tests that all possible paths for redeeming an -// accepted HTLC (on the commitment transaction) of the receiver work properly. -func TestTaprootReceiverHtlcSpend(t *testing.T) { - t.Parallel() - +func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) { // We'll start by creating the HTLC script tree (contains all 3 valid // spend paths), and also a mock spend transaction that we'll be // signing below. - htlcScriptTree := newTestReceiverHtlcScriptTree(t) + htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf) // TODO(roasbeef): issue with revoke key??? ctrl block even/odd @@ -891,6 +910,28 @@ func TestTaprootReceiverHtlcSpend(t *testing.T) { } } +// TestTaprootReceiverHtlcSpend tests that all possible paths for redeeming an +// accepted HTLC (on the commitment transaction) of the receiver work properly. +func TestTaprootReceiverHtlcSpend(t *testing.T) { + t.Parallel() + + for _, hasAuxLeaf := range []bool{true, false} { + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some( + txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + ), + ) + } + + testTaprootReceiverHtlcSpend(t, auxLeaf) + }) + } +} + type testCommitScriptTree struct { csvDelay uint32 @@ -905,7 +946,9 @@ type testCommitScriptTree struct { *CommitScriptTree } -func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) { +func newTestCommitScriptTree(local bool, + auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) { + selfKey, err := btcec.NewPrivateKey() if err != nil { return nil, err @@ -925,10 +968,11 @@ func newTestCommitScriptTree(local bool) (*testCommitScriptTree, error) { if local { commitScriptTree, err = NewLocalCommitScriptTree( csvDelay, selfKey.PubKey(), revokeKey.PubKey(), + auxLeaf, ) } else { commitScriptTree, err = NewRemoteCommitScriptTree( - selfKey.PubKey(), + selfKey.PubKey(), auxLeaf, ) } if err != nil { @@ -1020,12 +1064,8 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType, } } -// TestTaprootCommitScriptToSelf tests that the taproot script for redeeming -// one's output after a force close behaves as expected. -func TestTaprootCommitScriptToSelf(t *testing.T) { - t.Parallel() - - commitScriptTree, err := newTestCommitScriptTree(true) +func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) { + commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf) require.NoError(t, err) spendTx := wire.NewMsgTx(2) @@ -1187,6 +1227,26 @@ func TestTaprootCommitScriptToSelf(t *testing.T) { } } +// TestTaprootCommitScriptToSelf tests that the taproot script for redeeming +// one's output after a force close behaves as expected. +func TestTaprootCommitScriptToSelf(t *testing.T) { + t.Parallel() + + for _, hasAuxLeaf := range []bool{true, false} { + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } + + testTaprootCommitScriptToSelf(t, auxLeaf) + }) + } +} + func remoteCommitSweepWitGen(sigHash txscript.SigHashType, commitScriptTree *testCommitScriptTree) witnessGen { @@ -1220,12 +1280,8 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType, } } -// TestTaprootCommitScriptRemote tests that the remote party can properly sweep -// their output after force close. -func TestTaprootCommitScriptRemote(t *testing.T) { - t.Parallel() - - commitScriptTree, err := newTestCommitScriptTree(false) +func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) { + commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf) require.NoError(t, err) spendTx := wire.NewMsgTx(2) @@ -1364,6 +1420,26 @@ func TestTaprootCommitScriptRemote(t *testing.T) { } } +// TestTaprootCommitScriptRemote tests that the remote party can properly sweep +// their output after force close. +func TestTaprootCommitScriptRemote(t *testing.T) { + t.Parallel() + + for _, hasAuxLeaf := range []bool{true, false} { + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } + + testTaprootCommitScriptRemote(t, auxLeaf) + }) + } +} + type testAnchorScriptTree struct { sweepKey *btcec.PrivateKey @@ -1599,25 +1675,21 @@ type testSecondLevelHtlcTree struct { tapScriptRoot []byte } -func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) { +func newTestSecondLevelHtlcTree(t *testing.T, + auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree { + delayKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, err - } + require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, err - } + require.NoError(t, err) const csvDelay = 6 scriptTree, err := SecondLevelHtlcTapscriptTree( - delayKey.PubKey(), csvDelay, + delayKey.PubKey(), csvDelay, auxLeaf, ) - if err != nil { - return nil, err - } + require.NoError(t, err) tapScriptRoot := scriptTree.RootNode.TapHash() @@ -1626,9 +1698,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) { ) pkScript, err := PayToTaprootScript(htlcKey) - if err != nil { - return nil, err - } + require.NoError(t, err) const amt = 100 @@ -1643,7 +1713,7 @@ func newTestSecondLevelHtlcTree() (*testSecondLevelHtlcTree, error) { amt: amt, scriptTree: scriptTree, tapScriptRoot: tapScriptRoot[:], - }, nil + } } func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType, @@ -1713,13 +1783,8 @@ func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType, } } -// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly -// spend the second level HTLC script to resolve HTLCs. -func TestTaprootSecondLevelHtlcScript(t *testing.T) { - t.Parallel() - - htlcScriptTree, err := newTestSecondLevelHtlcTree() - require.NoError(t, err) +func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) { + htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf) spendTx := wire.NewMsgTx(2) spendTx.AddTxIn(&wire.TxIn{}) @@ -1879,3 +1944,23 @@ func TestTaprootSecondLevelHtlcScript(t *testing.T) { }) } } + +// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly +// spend the second level HTLC script to resolve HTLCs. +func TestTaprootSecondLevelHtlcScript(t *testing.T) { + t.Parallel() + + for _, hasAuxLeaf := range []bool{true, false} { + name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf) + t.Run(name, func(t *testing.T) { + var auxLeaf AuxTapLeaf + if hasAuxLeaf { + auxLeaf = fn.Some(txscript.NewBaseTapLeaf( + bytes.Repeat([]byte{0x01}, 32), + )) + } + + testTaprootSecondLevelHtlcScript(t, auxLeaf) + }) + } +} diff --git a/intercepted_forward.go b/intercepted_forward.go index 70590d0e41..791d4bd583 100644 --- a/intercepted_forward.go +++ b/intercepted_forward.go @@ -3,6 +3,7 @@ package lnd import ( "errors" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -51,6 +52,14 @@ func (f *interceptedForward) Resume() error { return ErrCannotResume } +// ResumeModified notifies the intention to resume an existing hold forward with +// a modified htlc. +func (f *interceptedForward) ResumeModified(_, _ fn.Option[lnwire.MilliSatoshi], + _ fn.Option[lnwire.CustomRecords]) error { + + return ErrCannotResume +} + // Fail notifies the intention to fail an existing hold forward with an // encrypted failure reason. func (f *interceptedForward) Fail(_ []byte) error { diff --git a/invoices/interface.go b/invoices/interface.go index f48aa37b60..c906da1c3f 100644 --- a/invoices/interface.go +++ b/invoices/interface.go @@ -207,3 +207,77 @@ type InvoiceUpdater interface { // Finalize finalizes the update before it is written to the database. Finalize(updateType UpdateType) error } + +// HtlcModifyRequest is the request that is passed to the client via callback +// during a HTLC interceptor session. The request contains the invoice that the +// given HTLC is attempting to settle. +type HtlcModifyRequest struct { + // WireCustomRecords are the custom records that were parsed from the + // HTLC wire message. These are the records of the current HTLC to be + // accepted/settled. All previously accepted/settled HTLCs for the same + // invoice are present in the Invoice field below. + WireCustomRecords lnwire.CustomRecords + + // ExitHtlcCircuitKey is the circuit key that identifies the HTLC which + // is involved in the invoice settlement. + ExitHtlcCircuitKey CircuitKey + + // ExitHtlcAmt is the amount of the HTLC which is involved in the + // invoice settlement. + ExitHtlcAmt lnwire.MilliSatoshi + + // ExitHtlcExpiry is the absolute expiry height of the HTLC which is + // involved in the invoice settlement. + ExitHtlcExpiry uint32 + + // CurrentHeight is the current block height. + CurrentHeight uint32 + + // Invoice is the invoice that is being intercepted. The HTLCs within + // the invoice are only those previously accepted/settled for the same + // invoice. + Invoice Invoice +} + +// HtlcModifyResponse is the response that the client should send back to the +// interceptor after processing the HTLC modify request. +type HtlcModifyResponse struct { + // AmountPaid is the amount that the client has decided the HTLC is + // actually worth. This might be different from the amount that the + // HTLC was originally sent with, in case additional value is carried + // along with it (which might be the case in custom channels). + AmountPaid lnwire.MilliSatoshi + + // CancelSet is a flag the interceptor client can set to force a + // cancellation of all HTLCs associated with the invoice that are + // currently accepted. Setting this field will ignore the AmountPaid + // field. + CancelSet bool +} + +// HtlcModifyCallback is a function that is called when an invoice is +// intercepted by the invoice interceptor. +type HtlcModifyCallback func(HtlcModifyRequest) (*HtlcModifyResponse, error) + +// HtlcModifier is an interface that allows an intercept client to register +// itself as a modifier of HTLCs that are settling an invoice. The client can +// then modify the HTLCs based on the invoice and the HTLC that is settling it. +type HtlcModifier interface { + // RegisterInterceptor sets the client callback function that will be + // called when an invoice is intercepted. If a callback is already set, + // an error is returned. The returned function must be used to reset the + // callback to nil once the client is done or disconnects. The read-only + // channel closes when the server stops. + RegisterInterceptor(HtlcModifyCallback) (func(), <-chan struct{}, error) +} + +// HtlcInterceptor is an interface that allows the invoice registry to let +// clients intercept invoices before they are settled. +type HtlcInterceptor interface { + // Intercept generates a new intercept session for the given invoice. + // The call blocks until the client has responded to the request or an + // error occurs. The response callback is only called if a session was + // created in the first place, which is only the case if a client is + // registered. + Intercept(HtlcModifyRequest, func(HtlcModifyResponse)) error +} diff --git a/invoices/invoice_expiry_watcher.go b/invoices/invoice_expiry_watcher.go index 7c5539636c..d7659dce3f 100644 --- a/invoices/invoice_expiry_watcher.go +++ b/invoices/invoice_expiry_watcher.go @@ -1,6 +1,7 @@ package invoices import ( + "errors" "fmt" "sync" "time" @@ -345,14 +346,14 @@ func (ew *InvoiceExpiryWatcher) cancelNextHeightExpiredInvoice() { // unexpected error. func (ew *InvoiceExpiryWatcher) expireInvoice(hash lntypes.Hash, force bool) { err := ew.cancelInvoice(hash, force) - switch err { - case nil: + switch { + case err == nil: - case ErrInvoiceAlreadyCanceled: + case errors.Is(err, ErrInvoiceAlreadyCanceled): - case ErrInvoiceAlreadySettled: + case errors.Is(err, ErrInvoiceAlreadySettled): - case ErrInvoiceNotFound: + case errors.Is(err, ErrInvoiceNotFound): // It's possible that the user has manually canceled the invoice // which will then be deleted by the garbage collector resulting // in an ErrInvoiceNotFound error. diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 472c55048e..9d54b6ad8d 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -74,6 +74,10 @@ type RegistryConfig struct { // KeysendHoldTime indicates for how long we want to accept and hold // spontaneous keysend payments. KeysendHoldTime time.Duration + + // HtlcInterceptor is an interface that allows the invoice registry to + // let clients intercept invoices before they are settled. + HtlcInterceptor HtlcInterceptor } // htlcReleaseEvent describes an htlc auto-release event. It is used to release @@ -914,6 +918,7 @@ func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error { func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32, circuitKey CircuitKey, hodlChan chan<- interface{}, + wireCustomRecords lnwire.CustomRecords, payload Payload) (HtlcResolution, error) { // Create the update context containing the relevant details of the @@ -925,6 +930,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, expiry: expiry, currentHeight: currentHeight, finalCltvRejectDelta: i.cfg.FinalCltvRejectDelta, + wireCustomRecords: wireCustomRecords, customRecords: payload.CustomRecords(), mpp: payload.MultiPath(), amp: payload.AMPRecord(), @@ -1019,13 +1025,66 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( ctx *invoiceUpdateCtx, hodlChan chan<- interface{}) ( HtlcResolution, invoiceExpiry, error) { + invoiceRef := ctx.invoiceRef() + setID := (*SetID)(ctx.setID()) + + // We need to look up the current state of the invoice in order to send + // the previously accepted/settled HTLCs to the interceptor. + existingInvoice, err := i.idb.LookupInvoice( + context.Background(), invoiceRef, + ) + switch { + case errors.Is(err, ErrInvoiceNotFound) || + errors.Is(err, ErrNoInvoicesCreated): + + // If the invoice was not found, return a failure resolution + // with an invoice not found result. + return NewFailResolution( + ctx.circuitKey, ctx.currentHeight, + ResultInvoiceNotFound, + ), nil, nil + + case err != nil: + ctx.log(err.Error()) + return nil, nil, err + } + + var cancelSet bool + + // Provide the invoice to the settlement interceptor to allow + // the interceptor's client an opportunity to manipulate the + // settlement process. + err = i.cfg.HtlcInterceptor.Intercept(HtlcModifyRequest{ + WireCustomRecords: ctx.wireCustomRecords, + ExitHtlcCircuitKey: ctx.circuitKey, + ExitHtlcAmt: ctx.amtPaid, + ExitHtlcExpiry: ctx.expiry, + CurrentHeight: uint32(ctx.currentHeight), + Invoice: existingInvoice, + }, func(resp HtlcModifyResponse) { + log.Debugf("Received invoice HTLC interceptor response: %v", + resp) + + if resp.AmountPaid != 0 { + ctx.amtPaid = resp.AmountPaid + } + + cancelSet = resp.CancelSet + }) + if err != nil { + err := fmt.Errorf("error during invoice HTLC interception: %w", + err) + ctx.log(err.Error()) + + return nil, nil, err + } + // We'll attempt to settle an invoice matching this rHash on disk (if // one exists). The callback will update the invoice state and/or htlcs. var ( resolution HtlcResolution updateSubscribers bool ) - callback := func(inv *Invoice) (*InvoiceUpdateDesc, error) { updateDesc, res, err := updateInvoice(ctx, inv) if err != nil { @@ -1037,13 +1096,22 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( updateDesc.State != nil // Assign resolution to outer scope variable. - resolution = res + if cancelSet { + // If a cancel signal was set for the htlc set, we set + // the resolution as a failure with an underpayment + // indication. Something was wrong with this htlc, so + // we probably can't settle the invoice at all. + resolution = NewFailResolution( + ctx.circuitKey, ctx.currentHeight, + ResultAmountTooLow, + ) + } else { + resolution = res + } return updateDesc, nil } - invoiceRef := ctx.invoiceRef() - setID := (*SetID)(ctx.setID()) invoice, err := i.idb.UpdateInvoice( context.Background(), invoiceRef, setID, callback, ) @@ -1056,8 +1124,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( ), nil, nil } - switch err { - case ErrInvoiceNotFound: + switch { + case errors.Is(err, ErrInvoiceNotFound): // If the invoice was not found, return a failure resolution // with an invoice not found result. return NewFailResolution( @@ -1065,13 +1133,13 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( ResultInvoiceNotFound, ), nil, nil - case ErrInvRefEquivocation: + case errors.Is(err, ErrInvRefEquivocation): return NewFailResolution( ctx.circuitKey, ctx.currentHeight, ResultInvoiceNotFound, ), nil, nil - case nil: + case err == nil: default: ctx.log(err.Error()) @@ -1080,6 +1148,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( var invoiceToExpire invoiceExpiry + log.Tracef("Settlement resolution: %T %v", resolution, resolution) + switch res := resolution.(type) { case *HtlcFailResolution: // Inspect latest htlc state on the invoice. If it is found, @@ -1212,7 +1282,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked( } // Now that the links have been notified of any state changes to their - // HTLCs, we'll go ahead and notify any clients wiaiting on the invoice + // HTLCs, we'll go ahead and notify any clients waiting on the invoice // state changes. if updateSubscribers { // We'll add a setID onto the notification, but only if this is diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index b0c0195227..3deb6405c0 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -23,6 +23,12 @@ import ( "github.com/stretchr/testify/require" ) +var ( + // htlcModifierMock is a mock implementation of the invoice HtlcModifier + // interface. + htlcModifierMock = &invpkg.MockHtlcModifier{} +) + // TestInvoiceRegistry is a master test which encompasses all tests using an // InvoiceDB instance. The purpose of this test is to be able to run all tests // with a custom DB instance, so that we can test the same logic with different @@ -239,7 +245,8 @@ func testSettleInvoice(t *testing.T, resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value, uint32(testCurrentHeight)+testInvoiceCltvDelta-1, - testCurrentHeight, getCircuitKey(10), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(10), hodlChan, + nil, testPayload, ) if err != nil { t.Fatal(err) @@ -255,7 +262,7 @@ func testSettleInvoice(t *testing.T, resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight, getCircuitKey(0), hodlChan, - testPayload, + nil, testPayload, ) if err != nil { t.Fatal(err) @@ -296,7 +303,8 @@ func testSettleInvoice(t *testing.T, // behaviour after a restart. resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(0), hodlChan, + nil, testPayload, ) require.NoError(t, err, "unexpected NotifyExitHopHtlc error") require.NotNil(t, resolution) @@ -310,7 +318,8 @@ func testSettleInvoice(t *testing.T, // paid invoice that may open up a probe vector. resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, - testCurrentHeight, getCircuitKey(1), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(1), hodlChan, + nil, testPayload, ) require.NoError(t, err, "unexpected NotifyExitHopHtlc error") require.NotNil(t, resolution) @@ -325,7 +334,8 @@ func testSettleInvoice(t *testing.T, // would have failed if it were the first payment. resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, - testCurrentHeight, getCircuitKey(2), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(2), hodlChan, + nil, testPayload, ) require.NoError(t, err, "unexpected NotifyExitHopHtlc error") require.NotNil(t, resolution) @@ -462,7 +472,8 @@ func testCancelInvoiceImpl(t *testing.T, gc bool, hodlChan := make(chan interface{}) resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoiceAmount, testHtlcExpiry, - testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatal("expected settlement of a canceled invoice to succeed") @@ -517,6 +528,7 @@ func testSettleHoldInvoice(t *testing.T, cfg := invpkg.RegistryConfig{ FinalCltvRejectDelta: testFinalCltvRejectDelta, Clock: clock, + HtlcInterceptor: htlcModifierMock, } expiryWatcher := invpkg.NewInvoiceExpiryWatcher( @@ -570,7 +582,8 @@ func testSettleHoldInvoice(t *testing.T, // should be possible. resolution, err := registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -582,7 +595,8 @@ func testSettleHoldInvoice(t *testing.T, // Test idempotency. resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -595,7 +609,8 @@ func testSettleHoldInvoice(t *testing.T, // is a replay. resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight+10, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight+10, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -608,7 +623,7 @@ func testSettleHoldInvoice(t *testing.T, // requirement. It should be rejected. resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, 1, testCurrentHeight, - getCircuitKey(1), hodlChan, testPayload, + getCircuitKey(1), hodlChan, nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -683,6 +698,7 @@ func testCancelHoldInvoice(t *testing.T, cfg := invpkg.RegistryConfig{ FinalCltvRejectDelta: testFinalCltvRejectDelta, Clock: testClock, + HtlcInterceptor: htlcModifierMock, } expiryWatcher := invpkg.NewInvoiceExpiryWatcher( cfg.Clock, 0, uint32(testCurrentHeight), nil, newMockNotifier(), @@ -711,7 +727,8 @@ func testCancelHoldInvoice(t *testing.T, // should be possible. resolution, err := registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -733,7 +750,8 @@ func testCancelHoldInvoice(t *testing.T, // accept height. resolution, err = registry.NotifyExitHopHtlc( testInvoicePaymentHash, amtPaid, testHtlcExpiry, - testCurrentHeight+1, getCircuitKey(0), hodlChan, testPayload, + testCurrentHeight+1, getCircuitKey(0), hodlChan, + nil, testPayload, ) if err != nil { t.Fatalf("expected settle to succeed but got %v", err) @@ -762,7 +780,7 @@ func testUnknownInvoice(t *testing.T, amt := lnwire.MilliSatoshi(100000) resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight, - getCircuitKey(0), hodlChan, testPayload, + getCircuitKey(0), hodlChan, nil, testPayload, ) if err != nil { t.Fatal("unexpected error") @@ -823,7 +841,7 @@ func testKeySendImpl(t *testing.T, keySendEnabled bool, resolution, err := ctx.registry.NotifyExitHopHtlc( hash, amt, expiry, testCurrentHeight, getCircuitKey(10), hodlChan, - invalidKeySendPayload, + nil, invalidKeySendPayload, ) if err != nil { t.Fatal(err) @@ -844,8 +862,8 @@ func testKeySendImpl(t *testing.T, keySendEnabled bool, } resolution, err = ctx.registry.NotifyExitHopHtlc( - hash, amt, expiry, - testCurrentHeight, getCircuitKey(10), hodlChan, keySendPayload, + hash, amt, expiry, testCurrentHeight, getCircuitKey(10), + hodlChan, nil, keySendPayload, ) if err != nil { t.Fatal(err) @@ -873,8 +891,8 @@ func testKeySendImpl(t *testing.T, keySendEnabled bool, // Replay the same keysend payment. We expect an identical resolution, // but no event should be generated. resolution, err = ctx.registry.NotifyExitHopHtlc( - hash, amt, expiry, - testCurrentHeight, getCircuitKey(10), hodlChan, keySendPayload, + hash, amt, expiry, testCurrentHeight, getCircuitKey(10), + hodlChan, nil, keySendPayload, ) require.Nil(t, err) checkSettleResolution(t, resolution, preimage) @@ -897,8 +915,8 @@ func testKeySendImpl(t *testing.T, keySendEnabled bool, } resolution, err = ctx.registry.NotifyExitHopHtlc( - hash2, amt, expiry, - testCurrentHeight, getCircuitKey(20), hodlChan, keySendPayload2, + hash2, amt, expiry, testCurrentHeight, getCircuitKey(20), + hodlChan, nil, keySendPayload2, ) require.Nil(t, err) @@ -956,8 +974,8 @@ func testHoldKeysendImpl(t *testing.T, timeoutKeysend bool, } resolution, err := ctx.registry.NotifyExitHopHtlc( - hash, amt, expiry, - testCurrentHeight, getCircuitKey(10), hodlChan, keysendPayload, + hash, amt, expiry, testCurrentHeight, getCircuitKey(10), + hodlChan, nil, keysendPayload, ) if err != nil { t.Fatal(err) @@ -1039,8 +1057,8 @@ func testMppPayment(t *testing.T, hodlChan1 := make(chan interface{}, 1) resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, - testHtlcExpiry, - testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload, + testHtlcExpiry, testCurrentHeight, getCircuitKey(10), + hodlChan1, nil, mppPayload, ) if err != nil { t.Fatal(err) @@ -1067,8 +1085,8 @@ func testMppPayment(t *testing.T, hodlChan2 := make(chan interface{}, 1) resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, - testHtlcExpiry, - testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload, + testHtlcExpiry, testCurrentHeight, getCircuitKey(11), + hodlChan2, nil, mppPayload, ) if err != nil { t.Fatal(err) @@ -1081,8 +1099,8 @@ func testMppPayment(t *testing.T, hodlChan3 := make(chan interface{}, 1) resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, - testHtlcExpiry, - testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload, + testHtlcExpiry, testCurrentHeight, getCircuitKey(12), + hodlChan3, nil, mppPayload, ) if err != nil { t.Fatal(err) @@ -1141,7 +1159,7 @@ func testMppPaymentWithOverpayment(t *testing.T, resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, testHtlcExpiry, testCurrentHeight, getCircuitKey(11), - hodlChan1, mppPayload, + hodlChan1, nil, mppPayload, ) if err != nil { t.Fatal(err) @@ -1156,7 +1174,7 @@ func testMppPaymentWithOverpayment(t *testing.T, testInvoicePaymentHash, testInvoice.Terms.Value/2+overpayment, testHtlcExpiry, testCurrentHeight, getCircuitKey(12), hodlChan2, - mppPayload, + nil, mppPayload, ) if err != nil { t.Fatal(err) @@ -1200,6 +1218,7 @@ func testInvoiceExpiryWithRegistry(t *testing.T, cfg := invpkg.RegistryConfig{ FinalCltvRejectDelta: testFinalCltvRejectDelta, Clock: testClock, + HtlcInterceptor: htlcModifierMock, } expiryWatcher := invpkg.NewInvoiceExpiryWatcher( @@ -1310,6 +1329,7 @@ func testOldInvoiceRemovalOnStart(t *testing.T, FinalCltvRejectDelta: testFinalCltvRejectDelta, Clock: testClock, GcCanceledInvoicesOnStartup: true, + HtlcInterceptor: htlcModifierMock, } expiryWatcher := invpkg.NewInvoiceExpiryWatcher( @@ -1439,7 +1459,7 @@ func testHeightExpiryWithRegistryImpl(t *testing.T, numParts int, settle bool, resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, htlcAmt, expiry, testCurrentHeight, getCircuitKey(uint64(i)), hodlChan, - payLoad, + nil, payLoad, ) require.NoError(t, err) require.Nil(t, resolution, "did not expect direct resolution") @@ -1541,8 +1561,8 @@ func testMultipleSetHeightExpiry(t *testing.T, hodlChan1 := make(chan interface{}, 1) resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, - testHtlcExpiry, - testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload, + testHtlcExpiry, testCurrentHeight, getCircuitKey(10), + hodlChan1, nil, mppPayload, ) require.NoError(t, err) require.Nil(t, resolution, "did not expect direct resolution") @@ -1571,7 +1591,8 @@ func testMultipleSetHeightExpiry(t *testing.T, hodlChan2 := make(chan interface{}, 1) resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, expiry, - testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload, + testCurrentHeight, getCircuitKey(11), hodlChan2, + nil, mppPayload, ) require.NoError(t, err) require.Nil(t, resolution, "did not expect direct resolution") @@ -1580,7 +1601,8 @@ func testMultipleSetHeightExpiry(t *testing.T, hodlChan3 := make(chan interface{}, 1) resolution, err = ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, testInvoice.Terms.Value/2, expiry, - testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload, + testCurrentHeight, getCircuitKey(12), hodlChan3, + nil, mppPayload, ) require.NoError(t, err) require.Nil(t, resolution, "did not expect direct resolution") @@ -1685,7 +1707,8 @@ func testSettleInvoicePaymentAddrRequired(t *testing.T, resolution, err := ctx.registry.NotifyExitHopHtlc( testInvoicePaymentHash, invoice.Terms.Value, uint32(testCurrentHeight)+testInvoiceCltvDelta-1, - testCurrentHeight, getCircuitKey(10), hodlChan, testPayload, + testCurrentHeight, getCircuitKey(10), hodlChan, + nil, testPayload, ) require.NoError(t, err) @@ -1774,9 +1797,9 @@ func testSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T, // no problem as we should allow these existing invoices to be settled. hodlChan := make(chan interface{}, 1) resolution, err := ctx.registry.NotifyExitHopHtlc( - testInvoicePaymentHash, testInvoiceAmount, - testHtlcExpiry, testCurrentHeight, - getCircuitKey(10), hodlChan, testPayload, + testInvoicePaymentHash, testInvoiceAmount, testHtlcExpiry, + testCurrentHeight, getCircuitKey(10), hodlChan, + nil, testPayload, ) require.NoError(t, err) @@ -1838,8 +1861,8 @@ func testAMPWithoutMPPPayload(t *testing.T, hodlChan := make(chan interface{}, 1) resolution, err := ctx.registry.NotifyExitHopHtlc( - lntypes.Hash{}, shardAmt, expiry, - testCurrentHeight, getCircuitKey(uint64(10)), hodlChan, + lntypes.Hash{}, shardAmt, expiry, testCurrentHeight, + getCircuitKey(uint64(10)), hodlChan, nil, payload, ) require.NoError(t, err) @@ -2007,8 +2030,8 @@ func testSpontaneousAmpPaymentImpl( } resolution, err := ctx.registry.NotifyExitHopHtlc( - child.Hash, shardAmt, expiry, - testCurrentHeight, getCircuitKey(uint64(i)), hodlChan, + child.Hash, shardAmt, expiry, testCurrentHeight, + getCircuitKey(uint64(i)), hodlChan, nil, payload, ) require.NoError(t, err) diff --git a/invoices/invoices.go b/invoices/invoices.go index 24118f1e5d..c48629c583 100644 --- a/invoices/invoices.go +++ b/invoices/invoices.go @@ -547,6 +547,11 @@ type InvoiceHTLC struct { // the htlc. CustomRecords record.CustomSet + // WireCustomRecords contains the custom key/value pairs that were only + // included in p2p wire message of the HTLC and not in the onion + // payload. + WireCustomRecords lnwire.CustomRecords + // AMP encapsulates additional data relevant to AMP HTLCs. This includes // the AMP onion record, in addition to the HTLC's payment hash and // preimage since these are unique to each AMP HTLC, and not the invoice @@ -566,6 +571,7 @@ func (h *InvoiceHTLC) Copy() *InvoiceHTLC { result.CustomRecords[k] = v } + result.WireCustomRecords = h.WireCustomRecords.Copy() result.AMP = h.AMP.Copy() return &result diff --git a/invoices/invoices_test.go b/invoices/invoices_test.go index 8002613557..b6efd6e557 100644 --- a/invoices/invoices_test.go +++ b/invoices/invoices_test.go @@ -176,7 +176,7 @@ func TestInvoices(t *testing.T) { test: testQueryInvoices, }, { - name: "CustomRecords", + name: "OutWireCustomRecords", test: testCustomRecords, }, { diff --git a/invoices/mock.go b/invoices/mock.go index 25c81a35d7..5d929c2274 100644 --- a/invoices/mock.go +++ b/invoices/mock.go @@ -83,3 +83,34 @@ func (m *MockInvoiceDB) DeleteCanceledInvoices(ctx context.Context) error { return args.Error(0) } + +// MockHtlcModifier is a mock implementation of the HtlcModifier interface. +type MockHtlcModifier struct { +} + +// Intercept generates a new intercept session for the given invoice. +// The call blocks until the client has responded to the request or an +// error occurs. The response callback is only called if a session was +// created in the first place, which is only the case if a client is +// registered. +func (m *MockHtlcModifier) Intercept( + _ HtlcModifyRequest, _ func(HtlcModifyResponse)) error { + + return nil +} + +// RegisterInterceptor sets the client callback function that will be +// called when an invoice is intercepted. If a callback is already set, +// an error is returned. The returned function must be used to reset the +// callback to nil once the client is done or disconnects. The read-only channel +// closes when the server stops. +func (m *MockHtlcModifier) RegisterInterceptor(HtlcModifyCallback) (func(), + <-chan struct{}, error) { + + return func() {}, make(chan struct{}), nil +} + +// Ensure that MockHtlcModifier implements the HtlcInterceptor and HtlcModifier +// interfaces. +var _ HtlcInterceptor = (*MockHtlcModifier)(nil) +var _ HtlcModifier = (*MockHtlcModifier)(nil) diff --git a/invoices/modification_interceptor.go b/invoices/modification_interceptor.go new file mode 100644 index 0000000000..47f0de80ba --- /dev/null +++ b/invoices/modification_interceptor.go @@ -0,0 +1,191 @@ +package invoices + +import ( + "errors" + "sync/atomic" + + "github.com/lightningnetwork/lnd/fn" +) + +var ( + // ErrInterceptorClientAlreadyConnected is an error that is returned + // when a client tries to connect to the interceptor service while + // another client is already connected. + ErrInterceptorClientAlreadyConnected = errors.New( + "interceptor client already connected", + ) + + // ErrInterceptorClientDisconnected is an error that is returned when + // the client disconnects during an interceptor session. + ErrInterceptorClientDisconnected = errors.New( + "interceptor client disconnected", + ) +) + +// safeCallback is a wrapper around a callback function that is safe for +// concurrent access. +type safeCallback struct { + // callback is the actual callback function that is called when an + // invoice is intercepted. This might be nil if no client is currently + // connected. + callback atomic.Pointer[HtlcModifyCallback] +} + +// Set atomically sets the callback function. If a callback is already set, an +// error is returned. The returned function can be used to reset the callback to +// nil once the client is done. +func (s *safeCallback) Set(callback HtlcModifyCallback) (func(), error) { + if !s.callback.CompareAndSwap(nil, &callback) { + return nil, ErrInterceptorClientAlreadyConnected + } + + return func() { + s.callback.Store(nil) + }, nil +} + +// IsConnected returns true if a client is currently connected. +func (s *safeCallback) IsConnected() bool { + return s.callback.Load() != nil +} + +// Exec executes the callback function if it is set. If the callback is not set, +// an error is returned. +func (s *safeCallback) Exec(req HtlcModifyRequest) (*HtlcModifyResponse, + error) { + + callback := s.callback.Load() + if callback == nil { + return nil, ErrInterceptorClientDisconnected + } + + return (*callback)(req) +} + +// HtlcModificationInterceptor is a service that intercepts HTLCs that aim to +// settle an invoice, enabling a subscribed client to modify certain aspects of +// those HTLCs. +type HtlcModificationInterceptor struct { + started atomic.Bool + stopped atomic.Bool + + // callback is the wrapped client callback function that is called when + // an invoice is intercepted. This function gives the client the ability + // to determine how the invoice should be settled. + callback *safeCallback + + // quit is a channel that is closed when the interceptor is stopped. + quit chan struct{} +} + +// NewHtlcModificationInterceptor creates a new HtlcModificationInterceptor. +func NewHtlcModificationInterceptor() *HtlcModificationInterceptor { + return &HtlcModificationInterceptor{ + callback: &safeCallback{}, + quit: make(chan struct{}), + } +} + +// Intercept generates a new intercept session for the given invoice. The call +// blocks until the client has responded to the request or an error occurs. The +// response callback is only called if a session was created in the first place, +// which is only the case if a client is registered. +func (s *HtlcModificationInterceptor) Intercept(clientRequest HtlcModifyRequest, + responseCallback func(HtlcModifyResponse)) error { + + // If there is no client callback set we will not handle the invoice + // further. + if !s.callback.IsConnected() { + log.Debugf("Not intercepting invoice with circuit key %v, no "+ + "intercept client connected", + clientRequest.ExitHtlcCircuitKey) + + return nil + } + + // We'll block until the client has responded to the request or an error + // occurs. + var ( + responseChan = make(chan *HtlcModifyResponse, 1) + errChan = make(chan error, 1) + ) + + // The callback function will block at the client's discretion. We will + // therefore execute it in a separate goroutine. We don't need a wait + // group because we wait for the response directly below. The caller + // needs to make sure they don't block indefinitely, by selecting on the + // quit channel they receive when registering the callback. + go func() { + log.Debugf("Waiting for client response from invoice HTLC "+ + "interceptor session with circuit key %v", + clientRequest.ExitHtlcCircuitKey) + + // By this point, we've already checked that the client callback + // is set. However, if the client disconnected since that check + // then Exec will return an error. + result, err := s.callback.Exec(clientRequest) + if err != nil { + _ = fn.SendOrQuit(errChan, err, s.quit) + + return + } + + _ = fn.SendOrQuit(responseChan, result, s.quit) + }() + + // Wait for the client to respond or an error to occur. + select { + case response := <-responseChan: + log.Debugf("Received invoice HTLC interceptor response: %v", + response) + + responseCallback(*response) + + return nil + + case err := <-errChan: + log.Errorf("Error from invoice HTLC interceptor session: %v", + err) + + return err + + case <-s.quit: + return ErrInterceptorClientDisconnected + } +} + +// RegisterInterceptor sets the client callback function that will be called +// when an invoice is intercepted. If a callback is already set, an error is +// returned. The returned function must be used to reset the callback to nil +// once the client is done or disconnects. +func (s *HtlcModificationInterceptor) RegisterInterceptor( + callback HtlcModifyCallback) (func(), <-chan struct{}, error) { + + done, err := s.callback.Set(callback) + return done, s.quit, err +} + +// Start starts the service. +func (s *HtlcModificationInterceptor) Start() error { + if !s.started.CompareAndSwap(false, true) { + return nil + } + + return nil +} + +// Stop stops the service. +func (s *HtlcModificationInterceptor) Stop() error { + if !s.stopped.CompareAndSwap(false, true) { + return nil + } + + close(s.quit) + + return nil +} + +// Ensure that HtlcModificationInterceptor implements the HtlcInterceptor and +// HtlcModifier interfaces. +var _ HtlcInterceptor = (*HtlcModificationInterceptor)(nil) +var _ HtlcModifier = (*HtlcModificationInterceptor)(nil) diff --git a/invoices/modification_interceptor_test.go b/invoices/modification_interceptor_test.go new file mode 100644 index 0000000000..286a390ff3 --- /dev/null +++ b/invoices/modification_interceptor_test.go @@ -0,0 +1,107 @@ +package invoices + +import ( + "fmt" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" +) + +var ( + defaultTimeout = 50 * time.Millisecond +) + +// TestHtlcModificationInterceptor tests the basic functionality of the HTLC +// modification interceptor. +func TestHtlcModificationInterceptor(t *testing.T) { + interceptor := NewHtlcModificationInterceptor() + request := HtlcModifyRequest{ + WireCustomRecords: lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType: []byte{1, 2, 3}, + }, + ExitHtlcCircuitKey: CircuitKey{ + ChanID: lnwire.NewShortChanIDFromInt(1), + HtlcID: 1, + }, + ExitHtlcAmt: 1234, + } + expectedResponse := HtlcModifyResponse{ + AmountPaid: 345, + } + interceptCallbackCalled := make(chan HtlcModifyRequest, 1) + successInterceptCallback := func( + req HtlcModifyRequest) (*HtlcModifyResponse, error) { + + interceptCallbackCalled <- req + + return &expectedResponse, nil + } + errorInterceptCallback := func( + req HtlcModifyRequest) (*HtlcModifyResponse, error) { + + interceptCallbackCalled <- req + + return nil, fmt.Errorf("something went wrong") + } + responseCallbackCalled := make(chan HtlcModifyResponse, 1) + responseCallback := func(resp HtlcModifyResponse) { + responseCallbackCalled <- resp + } + + // Create a session without setting a callback first. + err := interceptor.Intercept(request, responseCallback) + require.NoError(t, err) + + // Set the callback and create a new session. + done, _, err := interceptor.RegisterInterceptor( + successInterceptCallback, + ) + require.NoError(t, err) + + err = interceptor.Intercept(request, responseCallback) + require.NoError(t, err) + + // The intercept callback should be called now. + select { + case req := <-interceptCallbackCalled: + require.Equal(t, request, req) + + case <-time.After(defaultTimeout): + t.Fatal("intercept callback not called") + } + + // And the result should make it back to the response callback. + select { + case resp := <-responseCallbackCalled: + require.Equal(t, expectedResponse, resp) + + case <-time.After(defaultTimeout): + t.Fatal("response callback not called") + } + + // If we try to set a new callback without first returning the previous + // one, we should get an error. + _, _, err = interceptor.RegisterInterceptor(successInterceptCallback) + require.ErrorIs(t, err, ErrInterceptorClientAlreadyConnected) + + // Reset the callback, then try to set a new one. + done() + done2, _, err := interceptor.RegisterInterceptor(errorInterceptCallback) + require.NoError(t, err) + defer done2() + + // We should now get an error when intercepting. + err = interceptor.Intercept(request, responseCallback) + require.ErrorContains(t, err, "something went wrong") + + // The success callback should not be called. + select { + case resp := <-responseCallbackCalled: + t.Fatalf("unexpected response: %v", resp) + + case <-time.After(defaultTimeout): + // Expected. + } +} diff --git a/invoices/test_utils.go b/invoices/test_utils.go index c6e226083d..b21804346c 100644 --- a/invoices/test_utils.go +++ b/invoices/test_utils.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" - "fmt" "sync" "testing" "time" @@ -69,11 +68,7 @@ var ( testMessageSigner = zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := ecdsa.SignCompact(testPrivKey, hash, true) - if err != nil { - return nil, fmt.Errorf("can't sign the "+ - "message: %v", err) - } + sig := ecdsa.SignCompact(testPrivKey, hash, true) return sig, nil }, diff --git a/invoices/test_utils_test.go b/invoices/test_utils_test.go index ed7bfccddc..509b92d85c 100644 --- a/invoices/test_utils_test.go +++ b/invoices/test_utils_test.go @@ -122,12 +122,8 @@ var ( testMessageSigner = zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := ecdsa.SignCompact(testPrivKey, hash, true) - if err != nil { - return nil, fmt.Errorf("can't sign the "+ - "message: %v", err) - } - return sig, nil + + return ecdsa.SignCompact(testPrivKey, hash, true), nil }, } @@ -153,6 +149,7 @@ func defaultRegistryConfig() invpkg.RegistryConfig { return invpkg.RegistryConfig{ FinalCltvRejectDelta: testFinalCltvRejectDelta, HtlcHoldDuration: 30 * time.Second, + HtlcInterceptor: &invpkg.MockHtlcModifier{}, } } diff --git a/invoices/update.go b/invoices/update.go index d14bafee08..3137bbbfa7 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -21,12 +21,20 @@ type invoiceUpdateCtx struct { expiry uint32 currentHeight int32 finalCltvRejectDelta int32 - customRecords record.CustomSet - mpp *record.MPP - amp *record.AMP - metadata []byte - pathID *chainhash.Hash - totalAmtMsat lnwire.MilliSatoshi + + // wireCustomRecords are the custom records that were included with the + // HTLC wire message. + wireCustomRecords lnwire.CustomRecords + + // customRecords is a map of custom records that were included with the + // HTLC onion payload. + customRecords record.CustomSet + + mpp *record.MPP + amp *record.AMP + metadata []byte + pathID *chainhash.Hash + totalAmtMsat lnwire.MilliSatoshi } // invoiceRef returns an identifier that can be used to lookup or update the @@ -95,12 +103,14 @@ func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage, // acceptRes is a helper function which creates an accept resolution with // the information contained in the invoiceUpdateCtx and the accept resolution // result provided. -func (i invoiceUpdateCtx) acceptRes(outcome acceptResolutionResult) *htlcAcceptResolution { +func (i invoiceUpdateCtx) acceptRes( + outcome acceptResolutionResult) *htlcAcceptResolution { + return newAcceptResolution(i.circuitKey, outcome) } // updateInvoice is a callback for DB.UpdateInvoice that contains the invoice -// settlement logic. It returns a hltc resolution that indicates what the +// settlement logic. It returns a HTLC resolution that indicates what the // outcome of the update was. func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) ( *InvoiceUpdateDesc, HtlcResolution, error) { @@ -119,7 +129,7 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) ( pre := inv.Terms.PaymentPreimage // Terms.PaymentPreimage will be nil for AMP invoices. - // Set it to the HTLC's AMP Preimage instead. + // Set it to the HTLCs AMP Preimage instead. if pre == nil { pre = htlc.AMP.Preimage } @@ -180,13 +190,19 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc, paymentAddr = ctx.pathID[:] } + // For storage, we don't really care where the custom records came from. + // So we merge them together and store them in the same field. + customRecords := lnwire.CustomRecords( + ctx.customRecords, + ).MergedCopy(ctx.wireCustomRecords) + // Start building the accept descriptor. acceptDesc := &HtlcAcceptDesc{ Amt: ctx.amtPaid, Expiry: ctx.expiry, AcceptHeight: ctx.currentHeight, MppTotalAmt: totalAmt, - CustomRecords: ctx.customRecords, + CustomRecords: record.CustomSet(customRecords), } if ctx.amp != nil { @@ -223,7 +239,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc, htlcSet := inv.HTLCSet(setID, HtlcStateAccepted) - // Check whether total amt matches other htlcs in the set. + // Check whether total amt matches other HTLCs in the set. var newSetTotal lnwire.MilliSatoshi for _, htlc := range htlcSet { if totalAmt != htlc.MppTotalAmt { @@ -265,7 +281,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc, return &update, ctx.acceptRes(resultPartialAccepted), nil } - // Check to see if we can settle or this is an hold invoice and + // Check to see if we can settle or this is a hold invoice, and // we need to wait for the preimage. if inv.HodlInvoice { update.State = &InvoiceStateUpdateDesc{ @@ -434,13 +450,19 @@ func updateLegacy(ctx *invoiceUpdateCtx, return nil, ctx.failRes(ResultExpiryTooSoon), nil } + // For storage, we don't really care where the custom records came from. + // So we merge them together and store them in the same field. + customRecords := lnwire.CustomRecords( + ctx.customRecords, + ).MergedCopy(ctx.wireCustomRecords) + // Record HTLC in the invoice database. newHtlcs := map[CircuitKey]*HtlcAcceptDesc{ ctx.circuitKey: { Amt: ctx.amtPaid, Expiry: ctx.expiry, AcceptHeight: ctx.currentHeight, - CustomRecords: ctx.customRecords, + CustomRecords: record.CustomSet(customRecords), }, } diff --git a/invoices/update_invoice_test.go b/invoices/update_invoice_test.go index 42d370971d..6069fbecd7 100644 --- a/invoices/update_invoice_test.go +++ b/invoices/update_invoice_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/stretchr/testify/require" ) @@ -37,98 +38,147 @@ func TestUpdateHTLC(t *testing.T) { { name: "MPP accept", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, }, invState: ContractAccepted, setID: nil, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, + }, + expErr: nil, + }, + { + name: "MPP accept, copy custom records", + input: InvoiceHTLC{ + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: record.CustomSet{ + 0x01: []byte{0x02}, + 0xffff: []byte{0x04, 0x05, 0x06}, + }, + WireCustomRecords: lnwire.CustomRecords{ + 0x010101: []byte{0x02, 0x03}, + 0xffffff: []byte{0x44, 0x55, 0x66}, + }, + AMP: nil, + }, + invState: ContractAccepted, + setID: nil, + output: InvoiceHTLC{ + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: record.CustomSet{ + 0x01: []byte{0x02}, + 0xffff: []byte{0x04, 0x05, 0x06}, + }, + WireCustomRecords: lnwire.CustomRecords{ + 0x010101: []byte{0x02, 0x03}, + 0xffffff: []byte{0x44, 0x55, 0x66}, + }, + AMP: nil, }, expErr: nil, }, { name: "MPP settle", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, }, invState: ContractSettled, setID: nil, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, }, expErr: nil, }, { name: "MPP cancel", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, }, invState: ContractCanceled, setID: nil, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), - AMP: nil, + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), + AMP: nil, }, expErr: nil, }, { name: "AMP accept missing preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -138,14 +188,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -157,14 +208,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP accept invalid preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -174,14 +226,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -193,14 +246,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP accept valid preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -210,14 +264,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -229,14 +284,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP accept valid preimage different htlc set", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -246,14 +302,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &diffSetID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -265,14 +322,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP settle missing preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -282,14 +340,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -301,14 +360,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP settle invalid preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -318,14 +378,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -337,14 +398,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "AMP settle valid preimage", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -354,14 +416,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -377,14 +440,15 @@ func TestUpdateHTLC(t *testing.T) { // remain in the accepted state. name: "AMP settle valid preimage different htlc set", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -394,14 +458,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &diffSetID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -413,14 +478,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "accept invoice htlc already settled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -430,14 +496,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -449,14 +516,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "cancel invoice htlc already settled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -466,14 +534,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractCanceled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -485,14 +554,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "settle invoice htlc already settled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -502,14 +572,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateSettled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateSettled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -521,14 +592,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "cancel invoice", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: time.Time{}, - Expiry: 40, - State: HtlcStateAccepted, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: time.Time{}, + Expiry: 40, + State: HtlcStateAccepted, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -538,14 +610,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractCanceled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -557,14 +630,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "accept invoice htlc already canceled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -574,14 +648,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractAccepted, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -593,14 +668,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "cancel invoice htlc already canceled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -610,14 +686,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractCanceled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -629,14 +706,15 @@ func TestUpdateHTLC(t *testing.T) { { name: "settle invoice htlc already canceled", input: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, @@ -646,14 +724,15 @@ func TestUpdateHTLC(t *testing.T) { invState: ContractSettled, setID: &setID, output: InvoiceHTLC{ - Amt: 5000, - MppTotalAmt: 5000, - AcceptHeight: 100, - AcceptTime: testNow, - ResolveTime: testAlreadyNow, - Expiry: 40, - State: HtlcStateCanceled, - CustomRecords: make(record.CustomSet), + Amt: 5000, + MppTotalAmt: 5000, + AcceptHeight: 100, + AcceptTime: testNow, + ResolveTime: testAlreadyNow, + Expiry: 40, + State: HtlcStateCanceled, + CustomRecords: make(record.CustomSet), + WireCustomRecords: make(lnwire.CustomRecords), AMP: &InvoiceHtlcAMPData{ Record: *ampRecord, Hash: hash, diff --git a/itest/list_on_test.go b/itest/list_on_test.go index 82fcdd99b5..910b6387bf 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -358,6 +358,10 @@ var allTestCases = []*lntest.TestCase{ Name: "invoice routing hints", TestFunc: testInvoiceRoutingHints, }, + { + Name: "scid alias routing hints", + TestFunc: testScidAliasRoutingHints, + }, { Name: "multi-hop payments over private channels", TestFunc: testMultiHopOverPrivateChannels, @@ -450,6 +454,22 @@ var allTestCases = []*lntest.TestCase{ Name: "forward interceptor", TestFunc: testForwardInterceptorBasic, }, + { + Name: "forward interceptor modified htlc", + TestFunc: testForwardInterceptorModifiedHtlc, + }, + { + Name: "forward interceptor wire records", + TestFunc: testForwardInterceptorWireRecords, + }, + { + Name: "forward interceptor restart", + TestFunc: testForwardInterceptorRestart, + }, + { + Name: "invoice HTLC modifier basic", + TestFunc: testInvoiceHtlcModifierBasic, + }, { Name: "zero conf channel open", TestFunc: testZeroConfChannelOpen, diff --git a/itest/lnd_channel_force_close_test.go b/itest/lnd_channel_force_close_test.go index 34df5ab591..6d02804012 100644 --- a/itest/lnd_channel_force_close_test.go +++ b/itest/lnd_channel_force_close_test.go @@ -634,7 +634,7 @@ func channelForceClosureTest(ht *lntest.HarnessTest, // Recorf the HTLC outpoint, such that we can later // check whether it gets swept op := wire.OutPoint{ - Hash: *htlcTxID, + Hash: htlcTxID, Index: uint32(i), } htlcTxOutpointSet[op] = 0 diff --git a/itest/lnd_channel_funding_fund_max_test.go b/itest/lnd_channel_funding_fund_max_test.go index b836bdf621..43aec3c982 100644 --- a/itest/lnd_channel_funding_fund_max_test.go +++ b/itest/lnd_channel_funding_fund_max_test.go @@ -2,7 +2,7 @@ package itest import ( "context" - "fmt" + "errors" "testing" "github.com/btcsuite/btcd/btcutil" @@ -219,7 +219,7 @@ func runFundMaxTestCase(ht *lntest.HarnessTest, alice, bob *node.HarnessNode, // If we don't expect the channel opening to be // successful, simply check for an error. if testCase.chanOpenShouldFail { - expectedErr := fmt.Errorf(testCase.expectedErrStr) + expectedErr := errors.New(testCase.expectedErrStr) ht.OpenChannelAssertErr( alice, bob, chanParams, expectedErr, ) diff --git a/itest/lnd_channel_funding_utxo_selection_test.go b/itest/lnd_channel_funding_utxo_selection_test.go index 8504a17cb9..2b6d0cd301 100644 --- a/itest/lnd_channel_funding_utxo_selection_test.go +++ b/itest/lnd_channel_funding_utxo_selection_test.go @@ -2,6 +2,7 @@ package itest import ( "context" + "errors" "fmt" "testing" @@ -315,7 +316,7 @@ func runUtxoSelectionTestCase(ht *lntest.HarnessTest, alice, // If we don't expect the channel opening to be // successful, simply check for an error. if tc.chanOpenShouldFail { - expectedErr := fmt.Errorf(tc.expectedErrStr) + expectedErr := errors.New(tc.expectedErrStr) ht.OpenChannelAssertErr( alice, bob, chanParams, expectedErr, ) @@ -334,7 +335,7 @@ func runUtxoSelectionTestCase(ht *lntest.HarnessTest, alice, "locked by another subsystem: %s:%d", selectedOutpoints[0].TxidStr, selectedOutpoints[0].OutputIndex) - expectedErr := fmt.Errorf(expectedErrStr) + expectedErr := errors.New(expectedErrStr) ht.OpenChannelAssertErr( alice, bob, chanParams, expectedErr, ) diff --git a/itest/lnd_coop_close_external_delivery_test.go b/itest/lnd_coop_close_external_delivery_test.go index 7557923034..57c2da2f4f 100644 --- a/itest/lnd_coop_close_external_delivery_test.go +++ b/itest/lnd_coop_close_external_delivery_test.go @@ -10,13 +10,44 @@ import ( ) func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest) { - ht.Run("set delivery address at open", func(t *testing.T) { + ok := ht.Run("set P2WPKH delivery address at open", func(t *testing.T) { tt := ht.Subtest(t) - testCoopCloseWithExternalDeliveryImpl(tt, true) + testCoopCloseWithExternalDeliveryImpl( + tt, true, lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH, + ) }) - ht.Run("set delivery address at close", func(t *testing.T) { + // Abort the test if failed. + if !ok { + return + } + + ok = ht.Run("set P2WPKH delivery address at close", func(t *testing.T) { + tt := ht.Subtest(t) + testCoopCloseWithExternalDeliveryImpl( + tt, false, lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH, + ) + }) + // Abort the test if failed. + if !ok { + return + } + + ok = ht.Run("set P2TR delivery address at open", func(t *testing.T) { + tt := ht.Subtest(t) + testCoopCloseWithExternalDeliveryImpl( + tt, true, lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY, + ) + }) + // Abort the test if failed. + if !ok { + return + } + + ht.Run("set P2TR delivery address at close", func(t *testing.T) { tt := ht.Subtest(t) - testCoopCloseWithExternalDeliveryImpl(tt, false) + testCoopCloseWithExternalDeliveryImpl( + tt, false, lnrpc.AddressType_UNUSED_TAPROOT_PUBKEY, + ) }) } @@ -25,7 +56,7 @@ func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest) { // not. Some users set this value to be an address in a different wallet and // this should not affect our ability to accurately report the settled balance. func testCoopCloseWithExternalDeliveryImpl(ht *lntest.HarnessTest, - upfrontShutdown bool) { + upfrontShutdown bool, deliveryAddressType lnrpc.AddressType) { alice, bob := ht.Alice, ht.Bob ht.ConnectNodes(alice, bob) @@ -35,7 +66,7 @@ func testCoopCloseWithExternalDeliveryImpl(ht *lntest.HarnessTest, // wallet. We already correctly track settled balances when the address // is in the LND wallet. addr := bob.RPC.NewAddress(&lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH, + Type: deliveryAddressType, }) // Prepare for channel open. diff --git a/itest/lnd_coop_close_with_htlcs_test.go b/itest/lnd_coop_close_with_htlcs_test.go index 56f9e801b0..55d4d6d207 100644 --- a/itest/lnd_coop_close_with_htlcs_test.go +++ b/itest/lnd_coop_close_with_htlcs_test.go @@ -117,7 +117,7 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest) { ) // Wait for the close tx to be in the Mempool. - ht.AssertTxInMempool(&closeTxid) + ht.AssertTxInMempool(closeTxid) // Wait for it to get mined and finish tearing down. ht.AssertStreamChannelCoopClosed(alice, chanPoint, false, closeClient) diff --git a/itest/lnd_forward_interceptor_test.go b/itest/lnd_forward_interceptor_test.go index b5d08f5bf8..6148ba7f1a 100644 --- a/itest/lnd_forward_interceptor_test.go +++ b/itest/lnd_forward_interceptor_test.go @@ -1,18 +1,22 @@ package itest import ( + "bytes" "fmt" + "reflect" "strings" "time" "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" @@ -22,6 +26,9 @@ import ( var ( customTestKey uint64 = 394829 customTestValue = []byte{1, 3, 5} + + actionResumeModify = routerrpc.ResolveHoldForwardAction_RESUME_MODIFIED + actionResume = routerrpc.ResolveHoldForwardAction_RESUME ) type interceptorTestCase struct { @@ -344,17 +351,414 @@ func testForwardInterceptorBasic(ht *lntest.HarnessTest) { ht.CloseChannel(bob, cpBC) } +// testForwardInterceptorModifiedHtlc tests that the interceptor can modify the +// amount and custom records of an intercepted HTLC and resume it. +func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) { + // Initialize the test context with 3 connected nodes. + ts := newInterceptorTestScenario(ht) + + alice, bob, carol := ts.alice, ts.bob, ts.carol + + // Open and wait for channels. + const chanAmt = btcutil.Amount(300000) + p := lntest.OpenChannelParams{Amt: chanAmt} + reqs := []*lntest.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC := resp[0], resp[1] + + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) + + // Connect an interceptor to Bob's node. + bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor() + + // We're going to modify the payment amount and want Carol to accept the + // payment, so we set up an invoice acceptor on Dave. + carolAcceptor, carolCancel := carol.RPC.InvoiceHtlcModifier() + defer carolCancel() + + // Prepare the test cases. + invoiceValueAmtMsat := int64(20_000_000) + req := &lnrpc.Invoice{ValueMsat: invoiceValueAmtMsat} + addResponse := carol.RPC.AddInvoice(req) + invoice := carol.RPC.LookupInvoice(addResponse.RHash) + tc := &interceptorTestCase{ + amountMsat: invoiceValueAmtMsat, + invoice: invoice, + payAddr: invoice.PaymentAddr, + } + + // We initiate a payment from Alice. + done := make(chan struct{}) + go func() { + // Signal that all the payments have been sent. + defer close(done) + + ts.sendPaymentAndAssertAction(tc) + }() + + // We start the htlc interceptor with a simple implementation that saves + // all intercepted packets. These packets are held to simulate a + // pending payment. + packet := ht.ReceiveHtlcInterceptor(bobInterceptor) + + // Resume the intercepted HTLC with a modified amount and custom + // records. + customRecords := make(map[uint64][]byte) + + // Add custom records entry. + crKey := uint64(65537) + crValue := []byte("custom-records-test-value") + customRecords[crKey] = crValue + + // Modify the amount of the HTLC, so we send out less than the original + // amount. + const modifyAmount = 5_000_000 + newOutAmountMsat := packet.OutgoingAmountMsat - modifyAmount + err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + OutAmountMsat: newOutAmountMsat, + OutWireCustomRecords: customRecords, + Action: actionResumeModify, + }) + require.NoError(ht, err, "failed to send request") + + invoicePacket := ht.ReceiveInvoiceHtlcModification(carolAcceptor) + require.EqualValues( + ht, newOutAmountMsat, invoicePacket.ExitHtlcAmt, + ) + amtPaid := newOutAmountMsat + modifyAmount + err = carolAcceptor.Send(&invoicesrpc.HtlcModifyResponse{ + CircuitKey: invoicePacket.ExitHtlcCircuitKey, + AmtPaid: &amtPaid, + }) + require.NoError(ht, err, "carol acceptor response") + + // Cancel the context, which will disconnect Bob's interceptor. + cancelBobInterceptor() + + // Make sure all goroutines are finished. + select { + case <-done: + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for sending payment") + } + + // Assert that the payment was successful. + var preimage lntypes.Preimage + copy(preimage[:], invoice.RPreimage) + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED) + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) +} + +// testForwardInterceptorWireRecords tests that the interceptor can read any +// wire custom records provided by the sender of a payment as part of the +// update_add_htlc message. +func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) { + // Initialize the test context with 3 connected nodes. + ts := newInterceptorTestScenario(ht) + + alice, bob, carol, dave := ts.alice, ts.bob, ts.carol, ts.dave + + // Open and wait for channels. + const chanAmt = btcutil.Amount(300000) + p := lntest.OpenChannelParams{Amt: chanAmt} + reqs := []*lntest.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + {Local: carol, Remote: dave, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC, cpCD := resp[0], resp[1], resp[2] + + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) + + // Connect an interceptor to Bob's node. + bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor() + defer cancelBobInterceptor() + + // Also connect an interceptor on Carol's node to check whether we're + // relaying the TLVs send in update_add_htlc over Alice -> Bob on the + // Bob -> Carol link. + carolInterceptor, cancelCarolInterceptor := carol.RPC.HtlcInterceptor() + defer cancelCarolInterceptor() + + // We're going to modify the payment amount and want Dave to accept the + // payment, so we set up an invoice acceptor on Dave. + daveAcceptor, daveCancel := dave.RPC.InvoiceHtlcModifier() + defer daveCancel() + + req := &lnrpc.Invoice{ValueMsat: 20_000_000} + addResponse := dave.RPC.AddInvoice(req) + invoice := dave.RPC.LookupInvoice(addResponse.RHash) + + customRecords := map[uint64][]byte{ + 65537: []byte("test"), + } + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoice.PaymentRequest, + TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()), + FeeLimitMsat: noFeeLimitMsat, + FirstHopCustomRecords: customRecords, + } + + _ = alice.RPC.SendPayment(sendReq) + + // We start the htlc interceptor with a simple implementation that saves + // all intercepted packets. These packets are held to simulate a + // pending payment. + packet := ht.ReceiveHtlcInterceptor(bobInterceptor) + + require.Len(ht, packet.InWireCustomRecords, 1) + + val, ok := packet.InWireCustomRecords[65537] + require.True(ht, ok, "expected custom record") + require.Equal(ht, []byte("test"), val) + + // Just resume the payment on Bob. + err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + Action: actionResume, + }) + require.NoError(ht, err, "failed to send request") + + // Assert that the Alice -> Bob custom records in update_add_htlc are + // not propagated on the Bob -> Carol link. + packet = ht.ReceiveHtlcInterceptor(carolInterceptor) + require.Len(ht, packet.InWireCustomRecords, 0) + + // We're going to tell Carol to forward 5k sats less to Dave. We need to + // set custom records on the HTLC as well, to make sure the HTLC isn't + // rejected outright and actually gets to the invoice acceptor. + const modifyAmount = 5_000_000 + newOutAmountMsat := packet.OutgoingAmountMsat - modifyAmount + err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + OutAmountMsat: newOutAmountMsat, + OutWireCustomRecords: customRecords, + Action: actionResumeModify, + }) + require.NoError(ht, err, "carol interceptor response") + + // The payment should get to Dave, and we should be able to intercept + // and modify it, telling Dave to accept it. + invoicePacket := ht.ReceiveInvoiceHtlcModification(daveAcceptor) + require.EqualValues( + ht, newOutAmountMsat, invoicePacket.ExitHtlcAmt, + ) + amtPaid := newOutAmountMsat + modifyAmount + err = daveAcceptor.Send(&invoicesrpc.HtlcModifyResponse{ + CircuitKey: invoicePacket.ExitHtlcCircuitKey, + AmtPaid: &amtPaid, + }) + require.NoError(ht, err, "dave acceptor response") + + // Assert that the payment was successful. + var preimage lntypes.Preimage + copy(preimage[:], invoice.RPreimage) + ht.AssertPaymentStatus( + alice, preimage, lnrpc.Payment_SUCCEEDED, + func(p *lnrpc.Payment) error { + recordsEqual := reflect.DeepEqual( + p.FirstHopCustomRecords, + sendReq.FirstHopCustomRecords, + ) + if !recordsEqual { + return fmt.Errorf("expected custom records to "+ + "be equal, got %v expected %v", + p.FirstHopCustomRecords, + sendReq.FirstHopCustomRecords) + } + + return nil + }, + ) + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) + ht.CloseChannel(carol, cpCD) +} + +// testForwardInterceptorRestart tests that the interceptor can read any wire +// custom records provided by the sender of a payment as part of the +// update_add_htlc message and that those records are persisted correctly and +// re-sent on node restart. +func testForwardInterceptorRestart(ht *lntest.HarnessTest) { + // Initialize the test context with 3 connected nodes. + ts := newInterceptorTestScenario(ht) + + alice, bob, carol, dave := ts.alice, ts.bob, ts.carol, ts.dave + + // Open and wait for channels. + const chanAmt = btcutil.Amount(300000) + p := lntest.OpenChannelParams{Amt: chanAmt} + reqs := []*lntest.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + {Local: carol, Remote: dave, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC, cpCD := resp[0], resp[1], resp[2] + + // Make sure Alice is aware of channels Bob=>Carol and Carol=>Dave. + ht.AssertTopologyChannelOpen(alice, cpBC) + ht.AssertTopologyChannelOpen(alice, cpCD) + + // Connect an interceptor to Bob's node. + bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor() + + // Also connect an interceptor on Carol's node to check whether we're + // relaying the TLVs send in update_add_htlc over Alice -> Bob on the + // Bob -> Carol link. + carolInterceptor, cancelCarolInterceptor := carol.RPC.HtlcInterceptor() + defer cancelCarolInterceptor() + + req := &lnrpc.Invoice{ValueMsat: 50_000_000} + addResponse := dave.RPC.AddInvoice(req) + invoice := dave.RPC.LookupInvoice(addResponse.RHash) + + customRecords := map[uint64][]byte{ + 65537: []byte("test"), + } + + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoice.PaymentRequest, + TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()), + FeeLimitMsat: noFeeLimitMsat, + FirstHopCustomRecords: customRecords, + } + + _ = alice.RPC.SendPayment(sendReq) + + // We start the htlc interceptor with a simple implementation that saves + // all intercepted packets. These packets are held to simulate a + // pending payment. + packet := ht.ReceiveHtlcInterceptor(bobInterceptor) + + require.Len(ht, packet.InWireCustomRecords, 1) + require.Equal(ht, customRecords, packet.InWireCustomRecords) + + // We accept the payment at Bob and resume it, so it gets to Carol. + // This means the HTLC should now be fully locked in on Alice's side and + // any restart of the node should cause the payment to be resumed and + // the data to be persisted across restarts. + err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + Action: actionResume, + }) + require.NoError(ht, err, "failed to send request") + + // We don't resume the payment on Carol, so it should be held there. + + // The payment should now be in flight. + var preimage lntypes.Preimage + copy(preimage[:], invoice.RPreimage) + ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT) + + // We don't resume the payment on Carol, so it should be held there. + // We now restart first Bob, then Alice, so we can make sure we've + // started the interceptor again on Bob before Alice resumes the + // payment. + cancelBobInterceptor() + restartBob := ht.SuspendNode(bob) + restartAlice := ht.SuspendNode(alice) + + require.NoError(ht, restartBob(), "failed to restart bob") + bobInterceptor, cancelBobInterceptor = bob.RPC.HtlcInterceptor() + defer cancelBobInterceptor() + + require.NoError(ht, restartAlice(), "failed to restart alice") + + // We should get another notification about the held HTLC. + packet = ht.ReceiveHtlcInterceptor(bobInterceptor) + + require.Len(ht, packet.InWireCustomRecords, 1) + require.Equal(ht, customRecords, packet.InWireCustomRecords) + + err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + Action: actionResume, + }) + require.NoError(ht, err, "failed to send request") + + // And now we forward the payment at Carol. + packet = ht.ReceiveHtlcInterceptor(carolInterceptor) + require.Len(ht, packet.InWireCustomRecords, 0) + err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + Action: actionResume, + }) + require.NoError(ht, err, "failed to send request") + + // Assert that the payment was successful. + ht.AssertPaymentStatus( + alice, preimage, lnrpc.Payment_SUCCEEDED, + func(p *lnrpc.Payment) error { + recordsEqual := reflect.DeepEqual( + p.FirstHopCustomRecords, + sendReq.FirstHopCustomRecords, + ) + if !recordsEqual { + return fmt.Errorf("expected custom records to "+ + "be equal, got %v expected %v", + p.FirstHopCustomRecords, + sendReq.FirstHopCustomRecords) + } + + if len(p.Htlcs) != 1 { + return fmt.Errorf("expected 1 htlc, got %d", + len(p.Htlcs)) + } + + htlc := p.Htlcs[0] + rt := htlc.Route + if rt.FirstHopAmountMsat != rt.TotalAmtMsat { + return fmt.Errorf("expected first hop amount "+ + "to be %d, got %d", rt.TotalAmtMsat, + rt.FirstHopAmountMsat) + } + + cr := lnwire.CustomRecords(p.FirstHopCustomRecords) + recordData, err := cr.Serialize() + if err != nil { + return err + } + + if !bytes.Equal(rt.CustomChannelData, recordData) { + return fmt.Errorf("expected custom records to "+ + "be equal, got %x expected %x", + rt.CustomChannelData, recordData) + } + + return nil + }, + ) + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) + ht.CloseChannel(carol, cpCD) +} + // interceptorTestScenario is a helper struct to hold the test context and // provide the needed functionality. type interceptorTestScenario struct { - ht *lntest.HarnessTest - alice, bob, carol *node.HarnessNode + ht *lntest.HarnessTest + alice, bob, carol, dave *node.HarnessNode } // newInterceptorTestScenario initializes a new test scenario with three nodes // and connects them to have the following topology, // -// Alice --> Bob --> Carol +// Alice --> Bob --> Carol --> Dave // // Among them, Alice and Bob are standby nodes and Carol is a new node. func newInterceptorTestScenario( @@ -362,15 +766,21 @@ func newInterceptorTestScenario( alice, bob := ht.Alice, ht.Bob carol := ht.NewNode("carol", nil) + dave := ht.NewNode("dave", nil) ht.EnsureConnected(alice, bob) ht.EnsureConnected(bob, carol) + ht.EnsureConnected(carol, dave) + + // So that carol can open channels. + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) return &interceptorTestScenario{ ht: ht, alice: alice, bob: bob, carol: carol, + dave: dave, } } diff --git a/itest/lnd_funding_test.go b/itest/lnd_funding_test.go index 6e2f0070cd..8d715067d9 100644 --- a/itest/lnd_funding_test.go +++ b/itest/lnd_funding_test.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/labels" @@ -873,7 +874,7 @@ func testChannelFundingPersistence(ht *lntest.HarnessTest) { // channel has been opened. The funding transaction should be found // within the newly mined block. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, fundingTxID) + ht.AssertTxInBlock(block, *fundingTxID) // Get the height that our transaction confirmed at. height := int32(ht.CurrentHeight()) @@ -1066,13 +1067,13 @@ func testBatchChanFunding(ht *lntest.HarnessTest) { // Mine the batch transaction and check the network topology. block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, txHash) + ht.AssertTxInBlock(block, *txHash) ht.AssertTopologyChannelOpen(alice, chanPoint1) ht.AssertTopologyChannelOpen(alice, chanPoint2) ht.AssertTopologyChannelOpen(alice, chanPoint3) // Check if the change type from the batch_open_channel funding is P2TR. - rawTx := ht.GetRawTransaction(txHash) + rawTx := ht.GetRawTransaction(*txHash) require.Len(ht, rawTx.MsgTx().TxOut, 5) // Check the fee rate of the batch-opening transaction. We expect slight @@ -1192,6 +1193,7 @@ func deriveFundingShim(ht *lntest.HarnessTest, carol, dave *node.HarnessNode, _, fundingOutput, err = input.GenTaprootFundingScript( carolKey, daveKey, int64(chanSize), + fn.None[chainhash.Hash](), ) require.NoError(ht, err) diff --git a/itest/lnd_invoice_acceptor_test.go b/itest/lnd_invoice_acceptor_test.go new file mode 100644 index 0000000000..5a4a35ba05 --- /dev/null +++ b/itest/lnd_invoice_acceptor_test.go @@ -0,0 +1,358 @@ +package itest + +import ( + "context" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/node" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" +) + +// testInvoiceHtlcModifierBasic tests the basic functionality of the invoice +// HTLC modifier RPC server. +func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) { + ts := newAcceptorTestScenario(ht) + + alice, bob, carol := ts.alice, ts.bob, ts.carol + + // Open and wait for channels. + const chanAmt = btcutil.Amount(300000) + p := lntest.OpenChannelParams{Amt: chanAmt} + reqs := []*lntest.OpenChannelRequest{ + {Local: alice, Remote: bob, Param: p}, + {Local: bob, Remote: carol, Param: p}, + } + resp := ht.OpenMultiChannelsAsync(reqs) + cpAB, cpBC := resp[0], resp[1] + + // Make sure Alice is aware of channel Bob=>Carol. + ht.AssertTopologyChannelOpen(alice, cpBC) + + // Initiate Carol's invoice HTLC modifier. + invoiceModifier, cancelModifier := carol.RPC.InvoiceHtlcModifier() + + // We need to wait a bit to make sure the gRPC stream is established + // correctly. + time.Sleep(50 * time.Millisecond) + + // Make sure we get an error if we try to register a second modifier and + // then try to use it (the error won't be returned on connect, only on + // the first _read_ interaction on the stream). + mod2, err := carol.RPC.Invoice.HtlcModifier(context.Background()) + require.NoError(ht, err) + _, err = mod2.Recv() + require.ErrorContains( + ht, err, + invoices.ErrInterceptorClientAlreadyConnected.Error(), + ) + + // We also add a normal (forwarding) HTLC interceptor at Bob, so we can + // test that custom wire messages on the HTLC are forwarded correctly to + // the invoice HTLC interceptor. + bobInterceptor, bobInterceptorCancel := bob.RPC.HtlcInterceptor() + + // Prepare the test cases. + testCases := ts.prepareTestCases() + + for tcIdx, tc := range testCases { + ht.Logf("Running test case: %d", tcIdx) + + // Initiate a payment from Alice to Carol in a separate + // goroutine. We use a separate goroutine to avoid blocking the + // main goroutine where we will make use of the invoice + // acceptor. + sendPaymentDone := make(chan struct{}) + go func() { + // Signal that all the payments have been sent. + defer close(sendPaymentDone) + + _ = ts.sendPayment(tc) + }() + + // First, intercept the HTLC at Bob. + packet := ht.ReceiveHtlcInterceptor(bobInterceptor) + err := bobInterceptor.Send( + &routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: packet.IncomingCircuitKey, + OutAmountMsat: packet.OutgoingAmountMsat, + OutWireCustomRecords: tc.lastHopCustomRecords, + Action: actionResumeModify, + }, + ) + require.NoError(ht, err, "failed to send request") + + modifierRequest := ht.ReceiveInvoiceHtlcModification( + invoiceModifier, + ) + + // Sanity check the modifier request. + require.EqualValues( + ht, tc.invoiceAmountMsat, + modifierRequest.Invoice.ValueMsat, + ) + require.EqualValues( + ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt, + ) + require.Equal( + ht, tc.lastHopCustomRecords, + modifierRequest.ExitHtlcWireCustomRecords, + ) + + // For all other packets we resolve according to the test case. + amtPaid := uint64(tc.invoiceAmountMsat) + err = invoiceModifier.Send( + &invoicesrpc.HtlcModifyResponse{ + CircuitKey: modifierRequest.ExitHtlcCircuitKey, + AmtPaid: &amtPaid, + }, + ) + require.NoError(ht, err, "failed to send request") + + ht.Log("Waiting for payment send to complete") + select { + case <-sendPaymentDone: + ht.Log("Payment send attempt complete") + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for payment send") + } + + ht.Log("Ensure invoice status is settled") + require.Eventually(ht, func() bool { + updatedInvoice := carol.RPC.LookupInvoice( + tc.invoice.RHash, + ) + + return updatedInvoice.State == tc.finalInvoiceState + }, defaultTimeout, 1*time.Second) + + updatedInvoice := carol.RPC.LookupInvoice( + tc.invoice.RHash, + ) + + require.Len(ht, updatedInvoice.Htlcs, 1) + require.Equal( + ht, tc.lastHopCustomRecords, + updatedInvoice.Htlcs[0].CustomRecords, + ) + + // Make sure the custom channel data contains the encoded + // version of the custom records. + customRecords := lnwire.CustomRecords( + updatedInvoice.Htlcs[0].CustomRecords, + ) + encodedRecords, err := customRecords.Serialize() + require.NoError(ht, err) + + require.Equal( + ht, encodedRecords, + updatedInvoice.Htlcs[0].CustomChannelData, + ) + } + + // We don't need the HTLC interceptor at Bob anymore. + bobInterceptorCancel() + + // After the normal test cases, we test that we can shut down Carol + // while an HTLC interception is going on. We initiate a payment from + // Alice to Carol in a separate goroutine. We use a separate goroutine + // to avoid blocking the main goroutine where we will make use of the + // invoice acceptor. + sendPaymentDone := make(chan struct{}) + tc := &acceptorTestCase{ + invoiceAmountMsat: 9000, + sendAmountMsat: 9000, + } + ts.createInvoice(tc) + + go func() { + // Signal that all the payments have been sent. + defer close(sendPaymentDone) + + _ = ts.sendPayment(tc) + }() + + modifierRequest := ht.ReceiveInvoiceHtlcModification(invoiceModifier) + + // Sanity check the modifier request. + require.EqualValues( + ht, tc.invoiceAmountMsat, modifierRequest.Invoice.ValueMsat, + ) + require.EqualValues( + ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt, + ) + + // We don't send a response to the modifier, but instead shut down and + // restart Carol. + restart := ht.SuspendNode(carol) + require.NoError(ht, restart()) + + ht.Log("Waiting for payment send to complete") + select { + case <-sendPaymentDone: + ht.Log("Payment send attempt complete") + case <-time.After(defaultTimeout): + require.Fail(ht, "timeout waiting for payment send") + } + + cancelModifier() + + // Finally, close channels. + ht.CloseChannel(alice, cpAB) + ht.CloseChannel(bob, cpBC) +} + +// acceptorTestCase is a helper struct to hold test case data. +type acceptorTestCase struct { + // invoiceAmountMsat is the amount of the invoice. + invoiceAmountMsat int64 + + // sendAmountMsat is the amount that will be sent in the payment. + sendAmountMsat int64 + + // lastHopCustomRecords is a map of custom records that will be added + // to the last hop of the payment, by an HTLC interceptor at Bob. + lastHopCustomRecords map[uint64][]byte + + // finalInvoiceState is the expected eventual final state of the + // invoice. + finalInvoiceState lnrpc.Invoice_InvoiceState + + // payAddr is the payment address of the invoice. + payAddr []byte + + // invoice is the invoice that will be paid. + invoice *lnrpc.Invoice +} + +// acceptorTestScenario is a helper struct to hold the test context and provides +// helpful functionality. +type acceptorTestScenario struct { + ht *lntest.HarnessTest + alice, bob, carol *node.HarnessNode +} + +// newAcceptorTestScenario initializes a new test scenario with three nodes and +// connects them to have the following topology, +// +// Alice --> Bob --> Carol +// +// Among them, Alice and Bob are standby nodes and Carol is a new node. +func newAcceptorTestScenario(ht *lntest.HarnessTest) *acceptorTestScenario { + alice, bob := ht.Alice, ht.Bob + carol := ht.NewNode("carol", nil) + + ht.EnsureConnected(alice, bob) + ht.EnsureConnected(bob, carol) + + return &acceptorTestScenario{ + ht: ht, + alice: alice, + bob: bob, + carol: carol, + } +} + +// prepareTestCases prepares test cases. +func (c *acceptorTestScenario) prepareTestCases() []*acceptorTestCase { + cases := []*acceptorTestCase{ + // Send a payment with amount less than the invoice amount. + // Amount checking is skipped during the invoice settlement + // process. The sent payment should eventually result in the + // invoice being settled. + { + invoiceAmountMsat: 9000, + sendAmountMsat: 1000, + finalInvoiceState: lnrpc.Invoice_SETTLED, + }, + { + invoiceAmountMsat: 9000, + sendAmountMsat: 1000, + finalInvoiceState: lnrpc.Invoice_SETTLED, + lastHopCustomRecords: map[uint64][]byte{ + lnwire.MinCustomRecordsTlvType: {1, 2, 3}, + }, + }, + } + + for _, t := range cases { + c.createInvoice(t) + } + + return cases +} + +// createInvoice creates an invoice for the given test case. +func (c *acceptorTestScenario) createInvoice(tc *acceptorTestCase) { + inv := &lnrpc.Invoice{ValueMsat: tc.invoiceAmountMsat} + addResponse := c.carol.RPC.AddInvoice(inv) + invoice := c.carol.RPC.LookupInvoice(addResponse.RHash) + + // We'll need to also decode the returned invoice so we can grab the + // payment address which is now required for ALL payments. + payReq := c.carol.RPC.DecodePayReq(invoice.PaymentRequest) + + tc.invoice = invoice + tc.payAddr = payReq.PaymentAddr +} + +// buildRoute is a helper function to build a route with given hops. +func (c *acceptorTestScenario) buildRoute(amtMsat int64, + hops []*node.HarnessNode, payAddr []byte) *lnrpc.Route { + + rpcHops := make([][]byte, 0, len(hops)) + for _, hop := range hops { + k := hop.PubKeyStr + pubkey, err := route.NewVertexFromStr(k) + require.NoErrorf(c.ht, err, "error parsing %v: %v", k, err) + rpcHops = append(rpcHops, pubkey[:]) + } + + req := &routerrpc.BuildRouteRequest{ + AmtMsat: amtMsat, + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, + HopPubkeys: rpcHops, + PaymentAddr: payAddr, + } + + routeResp := c.alice.RPC.BuildRoute(req) + + return routeResp.Route +} + +// sendPaymentAndAssertAction sends a payment from alice to carol. +func (c *acceptorTestScenario) sendPayment( + tc *acceptorTestCase) *lnrpc.HTLCAttempt { + + // Build a route from alice to carol. + aliceBobCarolRoute := c.buildRoute( + tc.sendAmountMsat, []*node.HarnessNode{c.bob, c.carol}, + tc.payAddr, + ) + + // We need to cheat a bit. We are attempting to pay an invoice with + // amount X with an HTLC of amount Y that is less than X. And then we + // use the invoice HTLC interceptor to simulate the HTLC actually + // carrying amount X (even though the actual HTLC transaction output + // only has amount Y). But in order for the invoice to be settled, we + // need to make sure that the MPP total amount record in the last hop + // is set to the invoice amount. This would also be the case in a normal + // MPP payment, where each shard only pays a fraction of the invoice. + aliceBobCarolRoute.Hops[1].MppRecord.TotalAmtMsat = tc.invoiceAmountMsat + + // Send the payment. + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: tc.invoice.RHash, + Route: aliceBobCarolRoute, + } + + return c.alice.RPC.SendToRouteV2(sendReq) +} diff --git a/itest/lnd_multi-hop_test.go b/itest/lnd_multi-hop_test.go index d095936196..997c22bc39 100644 --- a/itest/lnd_multi-hop_test.go +++ b/itest/lnd_multi-hop_test.go @@ -736,8 +736,8 @@ func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest, ht.MineBlocksAndAssertNumTxes(1, 1) blocksMined++ - htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 2} - bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3} + htlcOutpoint := wire.OutPoint{Hash: closeTx, Index: 2} + bobCommitOutpoint := wire.OutPoint{Hash: closeTx, Index: 3} // Before the HTLC times out, we'll need to assert that Bob broadcasts // a sweep transaction for his commit output. Note that if the channel @@ -761,7 +761,7 @@ func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest, ) txid := commitSweepTx.TxHash() block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &txid) + ht.AssertTxInBlock(block, txid) blocksMined++ } @@ -789,7 +789,7 @@ func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest, // Next, we'll mine an additional block. This should serve to confirm // the second layer timeout transaction. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &timeoutTx) + ht.AssertTxInBlock(block, timeoutTx) // With the second layer timeout transaction confirmed, Bob should have // canceled backwards the HTLC that carol sent. @@ -1041,13 +1041,13 @@ func runMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest, // Mine a block to trigger the sweep. ht.MineEmptyBlocks(1) - bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3} + bobCommitOutpoint := wire.OutPoint{Hash: closeTx, Index: 3} bobCommitSweep := ht.AssertOutpointInMempool( bobCommitOutpoint, ) bobCommitSweepTxid := bobCommitSweep.TxHash() block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &bobCommitSweepTxid) + ht.AssertTxInBlock(block, bobCommitSweepTxid) } ht.AssertNumPendingForceClose(bob, 0) @@ -1226,7 +1226,7 @@ func runMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest, // Mine a block that should confirm the commit tx. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &closingTxid) + ht.AssertTxInBlock(block, closingTxid) // After the force close transaction is mined, Carol should offer her // second-level success HTLC tx and anchor to the sweeper. @@ -1303,7 +1303,7 @@ func runMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest, bobSecondLvlTx := ht.GetNumTxsFromMempool(1)[0] // It should spend from the commitment in the channel with Alice. - ht.AssertTxSpendFrom(bobSecondLvlTx, *bobForceClose) + ht.AssertTxSpendFrom(bobSecondLvlTx, bobForceClose) // At this point, Bob should have broadcast his second layer success // transaction, and should have sent it to the nursery for incubation. @@ -1362,7 +1362,7 @@ func runMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest, // Now Bob should have no pending channels anymore, as this just // resolved it by the confirmation of the sweep transaction. block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &bobSweepTxid) + ht.AssertTxInBlock(block, bobSweepTxid) // With the script-enforced lease commitment type, Alice and Bob still // haven't been able to sweep their respective commit outputs due to the @@ -1397,7 +1397,7 @@ func runMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest, // Both Alice and Bob show broadcast their commit sweeps. aliceCommitOutpoint := wire.OutPoint{ - Hash: *bobForceClose, Index: 3, + Hash: bobForceClose, Index: 3, } ht.AssertOutpointInMempool( aliceCommitOutpoint, @@ -1574,7 +1574,7 @@ func runMultiHopHtlcRemoteChainClaim(ht *lntest.HarnessTest, // Mine a block, which should contain: the commitment. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &closingTxid) + ht.AssertTxInBlock(block, closingTxid) // After the force close transaction is mined, Carol should offer her // second level HTLC tx to the sweeper, along with her anchor output. @@ -1628,12 +1628,12 @@ func runMultiHopHtlcRemoteChainClaim(ht *lntest.HarnessTest, bobHtlcSweepTxid := bobHtlcSweep.TxHash() // It should spend from the commitment in the channel with Alice. - ht.AssertTxSpendFrom(bobHtlcSweep, *aliceForceClose) + ht.AssertTxSpendFrom(bobHtlcSweep, aliceForceClose) // We'll now mine a block which should confirm Bob's HTLC sweep // transaction. block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &bobHtlcSweepTxid) + ht.AssertTxInBlock(block, bobHtlcSweepTxid) carolSecondLevelCSV-- // Now that the sweeping transaction has been confirmed, Bob should now @@ -1690,7 +1690,7 @@ func runMultiHopHtlcRemoteChainClaim(ht *lntest.HarnessTest, // Both Alice and Bob should broadcast their commit sweeps. aliceCommitOutpoint := wire.OutPoint{ - Hash: *aliceForceClose, Index: 3, + Hash: aliceForceClose, Index: 3, } ht.AssertOutpointInMempool(aliceCommitOutpoint) bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3} @@ -2162,7 +2162,7 @@ func runMultiHopHtlcAggregation(ht *lntest.HarnessTest, // level sweep. Now Bob should have no pending channels anymore, as // this just resolved it by the confirmation of the sweep transaction. block := ht.MineBlocksAndAssertNumTxes(1, numExpected)[0] - ht.AssertTxInBlock(block, &bobSweep) + ht.AssertTxInBlock(block, bobSweep) // For leased channels, we need to mine one more block to confirm Bob's // commit output sweep. diff --git a/itest/lnd_nonstd_sweep_test.go b/itest/lnd_nonstd_sweep_test.go index f83daa669f..1e20e2bfe7 100644 --- a/itest/lnd_nonstd_sweep_test.go +++ b/itest/lnd_nonstd_sweep_test.go @@ -111,7 +111,7 @@ func testNonStdSweepInner(ht *lntest.HarnessTest, address string) { for _, inp := range msgTx.TxIn { // Fetch the previous outpoint's value. prevOut := inp.PreviousOutPoint - ptx := ht.GetRawTransaction(&prevOut.Hash) + ptx := ht.GetRawTransaction(prevOut.Hash) pout := ptx.MsgTx().TxOut[prevOut.Index] inputVal += int(pout.Value) diff --git a/itest/lnd_open_channel_test.go b/itest/lnd_open_channel_test.go index 52ec622cf7..a196d71c5f 100644 --- a/itest/lnd_open_channel_test.go +++ b/itest/lnd_open_channel_test.go @@ -60,7 +60,7 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) { // channel on the original miner's chain, which should be considered // open. block := ht.MineBlocksAndAssertNumTxes(10, 1)[0] - ht.AssertTxInBlock(block, fundingTxID) + ht.AssertTxInBlock(block, *fundingTxID) _, err = tempMiner.Client.Generate(15) require.NoError(ht, err, "unable to generate blocks") @@ -116,7 +116,7 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) { // Cleanup by mining the funding tx again, then closing the channel. block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, fundingTxID) + ht.AssertTxInBlock(block, *fundingTxID) ht.CloseChannel(alice, chanPoint) } diff --git a/itest/lnd_psbt_test.go b/itest/lnd_psbt_test.go index 91d67ba376..58f21508fe 100644 --- a/itest/lnd_psbt_test.go +++ b/itest/lnd_psbt_test.go @@ -177,6 +177,17 @@ func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode, }, ) + // If this is a taproot channel, then we'll decode the PSBT to assert + // that an internal key is included. + if commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT { + decodedPSBT, err := psbt.NewFromRawBytes( + bytes.NewReader(tempPsbt), false, + ) + require.NoError(ht, err) + + require.Len(ht, decodedPSBT.Outputs[0].TaprootInternalKey, 32) + } + // Let's add a second channel to the batch. This time between Carol and // Alice. We will publish the batch TX once this channel funding is // complete. @@ -295,7 +306,7 @@ func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode, txHash := finalTx.TxHash() block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) ht.AssertTopologyChannelOpen(carol, chanPoint) ht.AssertTopologyChannelOpen(carol, chanPoint2) @@ -470,7 +481,7 @@ func runPsbtChanFundingExternal(ht *lntest.HarnessTest, carol, // Now we can mine a block to get the transaction confirmed, then wait // for the new channel to be propagated through the network. block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) ht.AssertTopologyChannelOpen(carol, chanPoint) ht.AssertTopologyChannelOpen(carol, chanPoint2) @@ -627,7 +638,7 @@ func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, carol, txHash := finalTx.TxHash() block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) ht.AssertTopologyChannelOpen(carol, chanPoint) // Next, to make sure the channel functions as normal, we'll make some @@ -1326,7 +1337,7 @@ func extractPublishAndMine(ht *lntest.HarnessTest, node *node.HarnessNode, // Mine one block which should contain two transactions. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] txHash := finalTx.TxHash() - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) return finalTx } @@ -1432,8 +1443,8 @@ func assertPsbtSpend(ht *lntest.HarnessTest, alice *node.HarnessNode, block := ht.MineBlocksAndAssertNumTxes(1, 2)[0] firstTxHash := prevTx.TxHash() secondTxHash := finalTx.TxHash() - ht.AssertTxInBlock(block, &firstTxHash) - ht.AssertTxInBlock(block, &secondTxHash) + ht.AssertTxInBlock(block, firstTxHash) + ht.AssertTxInBlock(block, secondTxHash) } // assertPsbtFundSignSpend funds a PSBT from the internal wallet and then @@ -1797,7 +1808,7 @@ func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) { txHash := finalTx.TxHash() block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) // Now we do the same but instead use preselected utxos to verify that // these utxos respects the utxo restrictions on sweeper unconfirmed @@ -1941,5 +1952,5 @@ func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) { txHash = finalTx.TxHash() block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] - ht.AssertTxInBlock(block, &txHash) + ht.AssertTxInBlock(block, txHash) } diff --git a/itest/lnd_revocation_test.go b/itest/lnd_revocation_test.go index 82415e0396..a8cef50070 100644 --- a/itest/lnd_revocation_test.go +++ b/itest/lnd_revocation_test.go @@ -128,7 +128,7 @@ func breachRetributionTestCase(ht *lntest.HarnessTest, // ordering, the first output in this breach tx is the to_remote // output. toRemoteOp := wire.OutPoint{ - Hash: *breachTXID, + Hash: breachTXID, Index: 0, } @@ -151,7 +151,7 @@ func breachRetributionTestCase(ht *lntest.HarnessTest, // Assert that all the inputs of this transaction are spending outputs // generated by Bob's breach transaction above. for _, txIn := range justiceTx.TxIn { - require.Equal(ht, *breachTXID, txIn.PreviousOutPoint.Hash, + require.Equal(ht, breachTXID, txIn.PreviousOutPoint.Hash, "justice tx not spending commitment utxo") } @@ -174,7 +174,7 @@ func breachRetributionTestCase(ht *lntest.HarnessTest, // transaction which was just accepted into the mempool. block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] justiceTxid := justiceTx.TxHash() - ht.AssertTxInBlock(block, &justiceTxid) + ht.AssertTxInBlock(block, justiceTxid) ht.AssertNodeNumChannels(carol, 0) @@ -318,7 +318,7 @@ func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, // ordering, the first output in this breach tx is the to_local // output. toLocalOp := wire.OutPoint{ - Hash: *breachTXID, + Hash: breachTXID, Index: 0, } @@ -363,7 +363,7 @@ func revokedCloseRetributionZeroValueRemoteOutputCase(ht *lntest.HarnessTest, // transaction which was just accepted into the mempool. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] justiceTxid := justiceTx.TxHash() - ht.AssertTxInBlock(block, &justiceTxid) + ht.AssertTxInBlock(block, justiceTxid) // At this point, Dave should have no pending channels. ht.AssertNodeNumChannels(dave, 0) @@ -567,7 +567,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // outputs to the second level before Dave broadcasts his justice tx, // we'll search through the mempool for a tx that matches the number of // expected inputs in the justice tx. - var justiceTxid *chainhash.Hash + var justiceTxid chainhash.Hash errNotFound := errors.New("justice tx not found") findJusticeTx := func() (*chainhash.Hash, error) { mempool := ht.GetRawMempool() @@ -579,14 +579,14 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // NOTE: We don't use `ht.GetRawTransaction` // which asserts a txid must be found as the HTLC // spending txes might be aggregated. - tx, err := ht.Miner().Client.GetRawTransaction(txid) + tx, err := ht.Miner().Client.GetRawTransaction(&txid) if err != nil { return nil, err } exNumInputs := 2 + numInvoices if len(tx.MsgTx().TxIn) == exNumInputs { - return txid, nil + return &txid, nil } } @@ -598,7 +598,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, if err != nil { return err } - justiceTxid = txid + justiceTxid = *txid return nil }, defaultTimeout) @@ -619,7 +619,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, return err } - justiceTxid = txid + justiceTxid = *txid return nil }, defaultTimeout) @@ -631,7 +631,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // isSecondLevelSpend checks that the passed secondLevelTxid is a // potentitial second level spend spending from the commit tx. isSecondLevelSpend := func(commitTxid, - secondLevelTxid *chainhash.Hash) bool { + secondLevelTxid chainhash.Hash) bool { secondLevel := ht.GetRawTransaction(secondLevelTxid) @@ -661,7 +661,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest, // the breach tx, Carol might have had the time to take an // output to the second level. In that case, check that the // justice tx is spending this second level output. - if isSecondLevelSpend(breachTXID, &txIn.PreviousOutPoint.Hash) { + if isSecondLevelSpend(breachTXID, txIn.PreviousOutPoint.Hash) { continue } require.Fail(ht, "justice tx not spending commitment utxo "+ diff --git a/itest/lnd_routing_test.go b/itest/lnd_routing_test.go index 0b1cfebe84..d92de5aba5 100644 --- a/itest/lnd_routing_test.go +++ b/itest/lnd_routing_test.go @@ -3,6 +3,7 @@ package itest import ( "encoding/hex" "fmt" + "math" "testing" "time" @@ -746,6 +747,194 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) { ht.ForceCloseChannel(alice, chanPointEve) } +// testScidAliasRoutingHints tests that dynamically created aliases via the RPC +// are properly used when routing. +func testScidAliasRoutingHints(ht *lntest.HarnessTest) { + const chanAmt = btcutil.Amount(800000) + + // Option-scid-alias is opt-in, as is anchors. + scidAliasArgs := []string{ + "--protocol.option-scid-alias", + "--protocol.anchors", + } + + // We'll have a network Bob -> Carol -> Dave in the end, with both Carol + // and Dave having an alias for the channel between them. + carol := ht.NewNode("Carol", scidAliasArgs) + dave := ht.NewNode("Dave", scidAliasArgs) + + ht.FundCoins(btcutil.SatoshiPerBitcoin, carol) + ht.FundCoins(btcutil.SatoshiPerBitcoin, dave) + + ht.ConnectNodes(carol, dave) + + // Create a channel between Carol and Dave, which uses the scid alias + // feature. + chanPointCD := ht.OpenChannel(carol, dave, lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + ScidAlias: true, + Private: true, + }) + + // Find the channel ID of the channel between Carol and Dave. + carolDaveChan := ht.QueryChannelByChanPoint(carol, chanPointCD) + + // Make sure we can't add an alias that's not actually in the alias + // range (which is the case with the base SCID). + err := carol.RPC.XAddLocalChanAliasesErr(&routerrpc.AddAliasesRequest{ + AliasMaps: []*lnrpc.AliasMap{ + { + BaseScid: carolDaveChan.ChanId, + Aliases: []uint64{ + carolDaveChan.ChanId, + }, + }, + }, + }) + require.ErrorContains(ht, err, routerrpc.ErrNoValidAlias.Error()) + + // Create an ephemeral alias that will be used as a routing hint. + ephemeralChanPoint := lnwire.ShortChannelID{ + BlockHeight: 16_100_000, + TxIndex: 1, + TxPosition: 1, + } + ephemeralAlias := ephemeralChanPoint.ToUint64() + ephemeralAliasMap := []*lnrpc.AliasMap{ + { + BaseScid: carolDaveChan.ChanId, + Aliases: []uint64{ + ephemeralAlias, + }, + }, + } + + // Add the alias to Carol. + carol.RPC.XAddLocalChanAliases(&routerrpc.AddAliasesRequest{ + AliasMaps: ephemeralAliasMap, + }) + + // We shouldn't be able to add the same alias again. + err = carol.RPC.XAddLocalChanAliasesErr(&routerrpc.AddAliasesRequest{ + AliasMaps: ephemeralAliasMap, + }) + require.ErrorContains(ht, err, routerrpc.ErrAliasAlreadyExists.Error()) + + // Add the alias to Dave. This isn't strictly needed for the test, as + // the payment will go from Bob -> Carol -> Dave, so only Carol needs + // to know about the alias. So we'll later on remove it again to + // demonstrate that. + dave.RPC.XAddLocalChanAliases(&routerrpc.AddAliasesRequest{ + AliasMaps: ephemeralAliasMap, + }) + + carolChans := carol.RPC.ListChannels(&lnrpc.ListChannelsRequest{}) + require.Len(ht, carolChans.Channels, 1, "expected one channel") + + // Get the alias scids for Carol's channel. + aliases := carolChans.Channels[0].AliasScids + + // There should be two aliases. + require.Len(ht, aliases, 2, "expected two aliases") + + // The ephemeral alias should be included. + require.Contains( + ht, aliases, ephemeralAlias, "expected ephemeral alias", + ) + + // List Dave's Channels. + daveChans := dave.RPC.ListChannels(&lnrpc.ListChannelsRequest{}) + + require.Len(ht, daveChans.Channels, 1, "expected one channel") + + // Get the alias scids for his channel. + aliases = daveChans.Channels[0].AliasScids + + // There should be two aliases. + require.Len(ht, aliases, 2, "expected two aliases") + + // The ephemeral alias should be included. + require.Contains( + ht, aliases, ephemeralAlias, "expected ephemeral alias", + ) + + // Now that we've asserted that the alias is properly set up, we'll + // delete the one for Dave again. The payment should still succeed. + dave.RPC.XDeleteLocalChanAliases(&routerrpc.DeleteAliasesRequest{ + AliasMaps: ephemeralAliasMap, + }) + + // Connect the existing Bob node with Carol via a public channel. + ht.ConnectNodes(ht.Bob, carol) + chanPointBC := ht.OpenChannel(ht.Bob, carol, lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + }) + + // Create the hop hint that Dave will use to craft his invoice. The + // goal here is to define only the ephemeral alias as a hop hint. + hopHint := &lnrpc.HopHint{ + NodeId: carol.PubKeyStr, + ChanId: ephemeralAlias, + FeeBaseMsat: uint32( + chainreg.DefaultBitcoinBaseFeeMSat, + ), + FeeProportionalMillionths: uint32( + chainreg.DefaultBitcoinFeeRate, + ), + CltvExpiryDelta: chainreg.DefaultBitcoinTimeLockDelta, + } + + // Define the invoice that Dave will add to his node. + invoice := &lnrpc.Invoice{ + Memo: "dynamic alias", + Value: int64(chanAmt / 4), + RouteHints: []*lnrpc.RouteHint{ + { + HopHints: []*lnrpc.HopHint{hopHint}, + }, + }, + } + + // Add the invoice and retrieve the payment request. + payReq := dave.RPC.AddInvoice(invoice).PaymentRequest + + // Now Alice will try to pay to that payment request. + timeout := time.Second * 15 + stream := ht.Bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: int32(timeout.Seconds()), + FeeLimitSat: math.MaxInt64, + }) + + // Payment should eventually succeed. + ht.AssertPaymentSucceedWithTimeout(stream, timeout) + + // Check that Dave's invoice appears as settled. + invoices := dave.RPC.ListInvoices(&lnrpc.ListInvoiceRequest{}) + require.Len(ht, invoices.Invoices, 1, "expected one invoice") + require.Equal(ht, invoices.Invoices[0].State, lnrpc.Invoice_SETTLED, + "expected settled invoice") + + // We'll now delete the alias again, but only on Carol's end. That + // should be enough to make the payment fail, since she doesn't know + // about the alias in the hop hint anymore. + carol.RPC.XDeleteLocalChanAliases(&routerrpc.DeleteAliasesRequest{ + AliasMaps: ephemeralAliasMap, + }) + payReq2 := dave.RPC.AddInvoice(invoice).PaymentRequest + stream2 := ht.Bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{ + PaymentRequest: payReq2, + TimeoutSeconds: int32(timeout.Seconds()), + FeeLimitSat: math.MaxInt64, + }) + ht.AssertPaymentStatusFromStream(stream2, lnrpc.Payment_FAILED) + + ht.CloseChannel(carol, chanPointCD) + ht.CloseChannel(ht.Bob, chanPointBC) +} + // testMultiHopOverPrivateChannels tests that private channels can be used as // intermediate hops in a route for payments. func testMultiHopOverPrivateChannels(ht *lntest.HarnessTest) { diff --git a/itest/lnd_signer_test.go b/itest/lnd_signer_test.go index bf9033cad8..9310699887 100644 --- a/itest/lnd_signer_test.go +++ b/itest/lnd_signer_test.go @@ -318,7 +318,7 @@ func assertSignOutputRaw(ht *lntest.HarnessTest, tx := wire.NewMsgTx(2) tx.TxIn = []*wire.TxIn{{ PreviousOutPoint: wire.OutPoint{ - Hash: *txid, + Hash: txid, Index: uint32(targetOutputIndex), }, }} diff --git a/itest/lnd_sweep_test.go b/itest/lnd_sweep_test.go index 5c4e63f3ea..3ccf6c8763 100644 --- a/itest/lnd_sweep_test.go +++ b/itest/lnd_sweep_test.go @@ -214,7 +214,7 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) { txns := ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx := ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Get the weight for Bob's anchor sweeping tx. txWeight := ht.CalculateTxWeight(sweepTx) @@ -281,7 +281,7 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx = ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Calculate the fee rate of Bob's new sweeping tx. feeRate = uint64(ht.CalculateTxFeeRate(sweepTx)) @@ -320,7 +320,7 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx = ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Calculate the fee of Bob's new sweeping tx. fee = uint64(ht.CalculateTxFee(sweepTx)) @@ -341,7 +341,7 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - currentSweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + currentSweepTx := ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Assert the anchor sweep tx stays unchanged. require.Equal(ht, sweepTx.TxHash(), currentSweepTx.TxHash()) @@ -553,7 +553,7 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) { txns := ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx := ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Get the weight for Bob's anchor sweeping tx. txWeight := ht.CalculateTxWeight(sweepTx) @@ -620,7 +620,7 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx = ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Calculate the fee rate of Bob's new sweeping tx. feeRate = uint64(ht.CalculateTxFeeRate(sweepTx)) @@ -659,7 +659,7 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + sweepTx = ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Calculate the fee of Bob's new sweeping tx. fee = uint64(ht.CalculateTxFee(sweepTx)) @@ -680,7 +680,7 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) { txns = ht.GetNumTxsFromMempool(2) // Find the sweeping tx. - currentSweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0] + currentSweepTx := ht.FindSweepingTxns(txns, 1, closeTxid)[0] // Assert the anchor sweep tx stays unchanged. require.Equal(ht, sweepTx.TxHash(), currentSweepTx.TxHash()) diff --git a/itest/lnd_taproot_test.go b/itest/lnd_taproot_test.go index 9434586a40..b30f8cdaab 100644 --- a/itest/lnd_taproot_test.go +++ b/itest/lnd_taproot_test.go @@ -171,7 +171,7 @@ func testTaprootComputeInputScriptKeySpendBip86(ht *lntest.HarnessTest, ht.AssertUTXOInWallet(alice, op, "") p2trOutpoint := wire.OutPoint{ - Hash: *txid, + Hash: txid, Index: uint32(p2trOutputIndex), } @@ -1413,7 +1413,7 @@ func clearWalletImportedTapscriptBalance(ht *lntest.HarnessTest, // Mine one block which should contain the sweep transaction. block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] sweepTxHash := sweepTx.TxHash() - ht.AssertTxInBlock(block, &sweepTxHash) + ht.AssertTxInBlock(block, sweepTxHash) } // testScriptHashLock returns a simple bitcoin script that locks the funds to @@ -1484,7 +1484,7 @@ func sendToTaprootOutput(ht *lntest.HarnessTest, hn *node.HarnessNode, txid := ht.AssertNumTxsInMempool(1)[0] p2trOutputIndex := ht.GetOutputIndex(txid, tapScriptAddr.String()) p2trOutpoint := wire.OutPoint{ - Hash: *txid, + Hash: txid, Index: uint32(p2trOutputIndex), } @@ -1849,7 +1849,7 @@ func testTaprootCoopClose(ht *lntest.HarnessTest) { // assertTaprootDeliveryUsed returns true if a Taproot addr was used in // the co-op close transaction. - assertTaprootDeliveryUsed := func(closingTxid *chainhash.Hash) bool { + assertTaprootDeliveryUsed := func(closingTxid chainhash.Hash) bool { tx := ht.GetRawTransaction(closingTxid) for _, txOut := range tx.MsgTx().TxOut { if !txscript.IsPayToTaproot(txOut.PkScript) { diff --git a/itest/lnd_wallet_import_test.go b/itest/lnd_wallet_import_test.go index b9cce9f1e5..d8fa84dce4 100644 --- a/itest/lnd_wallet_import_test.go +++ b/itest/lnd_wallet_import_test.go @@ -372,7 +372,7 @@ func fundChanAndCloseFromImportedAccount(ht *lntest.HarnessTest, srcNode, ) block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, txHash) + ht.AssertTxInBlock(block, *txHash) confBalanceAfterChan += chanChangeUtxoAmt ht.AssertWalletAccountBalance(srcNode, account, 0, 0) @@ -389,7 +389,7 @@ func fundChanAndCloseFromImportedAccount(ht *lntest.HarnessTest, srcNode, ) block := ht.MineBlocksAndAssertNumTxes(6, 1)[0] - ht.AssertTxInBlock(block, txHash) + ht.AssertTxInBlock(block, *txHash) confBalanceAfterChan += chanChangeUtxoAmt ht.AssertWalletAccountBalance( diff --git a/keychain/btcwallet.go b/keychain/btcwallet.go index 0df679df99..677de20da3 100644 --- a/keychain/btcwallet.go +++ b/keychain/btcwallet.go @@ -448,7 +448,8 @@ func (b *BtcWalletKeyRing) SignMessageCompact(keyLoc KeyLocator, } else { digest = chainhash.HashB(msg) } - return ecdsa.SignCompact(privKey, digest, true) + + return ecdsa.SignCompact(privKey, digest, true), nil } // SignMessageSchnorr uses the Schnorr signature algorithm to sign the given diff --git a/keychain/signer.go b/keychain/signer.go index 9605e72ec1..fa2b70762d 100644 --- a/keychain/signer.go +++ b/keychain/signer.go @@ -85,7 +85,8 @@ func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte, } else { digest = chainhash.HashB(msg) } - return ecdsa.SignCompact(p.privKey, digest, true) + + return ecdsa.SignCompact(p.privKey, digest, true), nil } var _ SingleKeyMessageSigner = (*PubKeyMessageSigner)(nil) diff --git a/lncfg/protocol.go b/lncfg/protocol.go index d86613188a..c670b18947 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 e568b6b9aa..5c5150a0ef 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/lnd.go b/lnd.go index 38f5c0d759..b69383a893 100644 --- a/lnd.go +++ b/lnd.go @@ -456,7 +456,8 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, defer cleanUp() partialChainControl, walletConfig, cleanUp, err := implCfg.BuildWalletConfig( - ctx, dbs, interceptorChain, grpcListeners, + ctx, dbs, &implCfg.AuxComponents, interceptorChain, + grpcListeners, ) if err != nil { return mkErr("error creating wallet config: %v", err) @@ -600,6 +601,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, activeChainControl.Cfg.WalletUnlockParams.ChansToRestore, multiAcceptor, torController, tlsManager, leaderElector, + implCfg, ) if err != nil { return mkErr("unable to create server: %v", err) @@ -636,6 +638,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, err = rpcServer.addDeps( server, interceptorChain.MacaroonService(), cfg.SubRPCServers, atplManager, server.invoices, tower, multiAcceptor, + server.invoiceHtlcModifier, ) if err != nil { return mkErr("unable to add deps to RPC server: %v", err) diff --git a/lnrpc/Dockerfile b/lnrpc/Dockerfile index 0f74d5e360..253e54f16a 100644 --- a/lnrpc/Dockerfile +++ b/lnrpc/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.5-bookworm +FROM golang:1.22.6-bookworm RUN apt-get update && apt-get install -y \ git \ diff --git a/lnrpc/gen_protos_docker.sh b/lnrpc/gen_protos_docker.sh index edf97e06b9..1472f5c5fa 100755 --- a/lnrpc/gen_protos_docker.sh +++ b/lnrpc/gen_protos_docker.sh @@ -6,7 +6,7 @@ set -e DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" # golang docker image version used in this script. -GO_IMAGE=docker.io/library/golang:1.21.4-alpine +GO_IMAGE=docker.io/library/golang:1.22.6-alpine PROTOBUF_VERSION=$(docker run --rm -v $DIR/../:/lnd -w /lnd $GO_IMAGE \ go list -f '{{.Version}}' -m google.golang.org/protobuf) diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index dcb1bef71e..df274f2da7 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -587,7 +587,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, return ecdsa.SignCompact( ephemKey, chainhash.HashB(msg), true, - ) + ), nil }, }) if err != nil { diff --git a/lnrpc/invoicesrpc/config_active.go b/lnrpc/invoicesrpc/config_active.go index a5b29c32a2..9568ed8919 100644 --- a/lnrpc/invoicesrpc/config_active.go +++ b/lnrpc/invoicesrpc/config_active.go @@ -10,6 +10,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/netann" + "google.golang.org/protobuf/proto" ) // Config is the primary configuration struct for the invoices RPC server. It @@ -30,6 +31,11 @@ type Config struct { // created by the daemon. InvoiceRegistry *invoices.InvoiceRegistry + // HtlcModifier is a service which intercepts invoice HTLCs during the + // settlement phase, enabling a subscribed client to modify certain + // aspects of those HTLCs. + HtlcModifier invoices.HtlcModifier + // IsChannelActive is used to generate valid hop hints. IsChannelActive func(chanID lnwire.ChannelID) bool @@ -64,4 +70,8 @@ type Config struct { // GetAlias returns the peer's alias SCID if it exists given the // 32-byte ChannelID. GetAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error) + + // ParseAuxData is a function that can be used to parse the auxiliary + // data from the invoice. + ParseAuxData func(message proto.Message) error } diff --git a/lnrpc/invoicesrpc/htlc_modifier.go b/lnrpc/invoicesrpc/htlc_modifier.go new file mode 100644 index 0000000000..00259962cf --- /dev/null +++ b/lnrpc/invoicesrpc/htlc_modifier.go @@ -0,0 +1,87 @@ +package invoicesrpc + +import ( + "fmt" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/lnwire" +) + +// htlcModifier is a helper struct that handles the lifecycle of an RPC invoice +// HTLC modifier server instance. +// +// This struct handles passing send and receive RPC messages between the client +// and the invoice service. +type htlcModifier struct { + // chainParams is required to properly marshall an invoice for RPC. + chainParams *chaincfg.Params + + // serverStream is a bidirectional RPC server stream to send invoices to + // a client and receive accept responses from the client. + serverStream Invoices_HtlcModifierServer +} + +// newHtlcModifier creates a new RPC invoice HTLC modifier handler. +func newHtlcModifier(params *chaincfg.Params, + serverStream Invoices_HtlcModifierServer) *htlcModifier { + + return &htlcModifier{ + chainParams: params, + serverStream: serverStream, + } +} + +// onIntercept is called when an invoice HTLC is intercepted by the invoice HTLC +// modifier. This method sends the invoice and the current HTLC to the client. +func (r *htlcModifier) onIntercept( + req invoices.HtlcModifyRequest) (*invoices.HtlcModifyResponse, error) { + + // Convert the circuit key to an RPC circuit key. + rpcCircuitKey := &CircuitKey{ + ChanId: req.ExitHtlcCircuitKey.ChanID.ToUint64(), + HtlcId: req.ExitHtlcCircuitKey.HtlcID, + } + + // Convert the invoice to an RPC invoice. + rpcInvoice, err := CreateRPCInvoice(&req.Invoice, r.chainParams) + if err != nil { + return nil, err + } + + // Send the modification request to the client. + err = r.serverStream.Send(&HtlcModifyRequest{ + Invoice: rpcInvoice, + ExitHtlcCircuitKey: rpcCircuitKey, + ExitHtlcAmt: uint64(req.ExitHtlcAmt), + ExitHtlcExpiry: req.ExitHtlcExpiry, + CurrentHeight: req.CurrentHeight, + ExitHtlcWireCustomRecords: req.WireCustomRecords, + }) + if err != nil { + return nil, err + } + + // Then wait for the client to respond. + resp, err := r.serverStream.Recv() + if err != nil { + return nil, err + } + + if resp.CircuitKey == nil { + return nil, fmt.Errorf("missing circuit key") + } + + log.Tracef("Resolving invoice HTLC modifier response %v", resp) + + // Pass the resolution to the modifier. + var amtPaid lnwire.MilliSatoshi + if resp.AmtPaid != nil { + amtPaid = lnwire.MilliSatoshi(*resp.AmtPaid) + } + + return &invoices.HtlcModifyResponse{ + AmountPaid: amtPaid, + CancelSet: resp.CancelSet, + }, nil +} diff --git a/lnrpc/invoicesrpc/invoices.pb.go b/lnrpc/invoicesrpc/invoices.pb.go index 27cf424d30..ca2b54e7d1 100644 --- a/lnrpc/invoicesrpc/invoices.pb.go +++ b/lnrpc/invoicesrpc/invoices.pb.go @@ -617,6 +617,231 @@ func (*LookupInvoiceMsg_PaymentAddr) isLookupInvoiceMsg_InvoiceRef() {} func (*LookupInvoiceMsg_SetId) isLookupInvoiceMsg_InvoiceRef() {} +// CircuitKey is a unique identifier for an HTLC. +type CircuitKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the channel that the is part of this circuit. + ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` + // The index of the incoming htlc in the incoming channel. + HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` +} + +func (x *CircuitKey) Reset() { + *x = CircuitKey{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CircuitKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CircuitKey) ProtoMessage() {} + +func (x *CircuitKey) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CircuitKey.ProtoReflect.Descriptor instead. +func (*CircuitKey) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{8} +} + +func (x *CircuitKey) GetChanId() uint64 { + if x != nil { + return x.ChanId + } + return 0 +} + +func (x *CircuitKey) GetHtlcId() uint64 { + if x != nil { + return x.HtlcId + } + return 0 +} + +type HtlcModifyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The invoice the intercepted HTLC is attempting to settle. The HTLCs in + // the invoice are only HTLCs that have already been accepted or settled, + // not including the current intercepted HTLC. + Invoice *lnrpc.Invoice `protobuf:"bytes,1,opt,name=invoice,proto3" json:"invoice,omitempty"` + // The unique identifier of the HTLC of this intercepted HTLC. + ExitHtlcCircuitKey *CircuitKey `protobuf:"bytes,2,opt,name=exit_htlc_circuit_key,json=exitHtlcCircuitKey,proto3" json:"exit_htlc_circuit_key,omitempty"` + // The amount in milli-satoshi that the exit HTLC is attempting to pay. + ExitHtlcAmt uint64 `protobuf:"varint,3,opt,name=exit_htlc_amt,json=exitHtlcAmt,proto3" json:"exit_htlc_amt,omitempty"` + // The absolute expiry height of the exit HTLC. + ExitHtlcExpiry uint32 `protobuf:"varint,4,opt,name=exit_htlc_expiry,json=exitHtlcExpiry,proto3" json:"exit_htlc_expiry,omitempty"` + // The current block height. + CurrentHeight uint32 `protobuf:"varint,5,opt,name=current_height,json=currentHeight,proto3" json:"current_height,omitempty"` + // The wire message custom records of the exit HTLC. + ExitHtlcWireCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=exit_htlc_wire_custom_records,json=exitHtlcWireCustomRecords,proto3" json:"exit_htlc_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *HtlcModifyRequest) Reset() { + *x = HtlcModifyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcModifyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcModifyRequest) ProtoMessage() {} + +func (x *HtlcModifyRequest) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcModifyRequest.ProtoReflect.Descriptor instead. +func (*HtlcModifyRequest) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{9} +} + +func (x *HtlcModifyRequest) GetInvoice() *lnrpc.Invoice { + if x != nil { + return x.Invoice + } + return nil +} + +func (x *HtlcModifyRequest) GetExitHtlcCircuitKey() *CircuitKey { + if x != nil { + return x.ExitHtlcCircuitKey + } + return nil +} + +func (x *HtlcModifyRequest) GetExitHtlcAmt() uint64 { + if x != nil { + return x.ExitHtlcAmt + } + return 0 +} + +func (x *HtlcModifyRequest) GetExitHtlcExpiry() uint32 { + if x != nil { + return x.ExitHtlcExpiry + } + return 0 +} + +func (x *HtlcModifyRequest) GetCurrentHeight() uint32 { + if x != nil { + return x.CurrentHeight + } + return 0 +} + +func (x *HtlcModifyRequest) GetExitHtlcWireCustomRecords() map[uint64][]byte { + if x != nil { + return x.ExitHtlcWireCustomRecords + } + return nil +} + +type HtlcModifyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The circuit key of the HTLC that the client wants to modify. + CircuitKey *CircuitKey `protobuf:"bytes,1,opt,name=circuit_key,json=circuitKey,proto3" json:"circuit_key,omitempty"` + // The modified amount in milli-satoshi that the exit HTLC is paying. This + // value can be different from the actual on-chain HTLC amount, in case the + // HTLC carries other valuable items, as can be the case with custom channel + // types. + AmtPaid *uint64 `protobuf:"varint,2,opt,name=amt_paid,json=amtPaid,proto3,oneof" json:"amt_paid,omitempty"` + // This flag indicates whether the HTLCs associated with the invoices should + // be cancelled. The interceptor client may set this field if some + // unexpected behavior is encountered. Setting this will ignore the amt_paid + // field. + CancelSet bool `protobuf:"varint,3,opt,name=cancel_set,json=cancelSet,proto3" json:"cancel_set,omitempty"` +} + +func (x *HtlcModifyResponse) Reset() { + *x = HtlcModifyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcModifyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcModifyResponse) ProtoMessage() {} + +func (x *HtlcModifyResponse) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcModifyResponse.ProtoReflect.Descriptor instead. +func (*HtlcModifyResponse) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{10} +} + +func (x *HtlcModifyResponse) GetCircuitKey() *CircuitKey { + if x != nil { + return x.CircuitKey + } + return nil +} + +func (x *HtlcModifyResponse) GetAmtPaid() uint64 { + if x != nil && x.AmtPaid != nil { + return *x.AmtPaid + } + return 0 +} + +func (x *HtlcModifyResponse) GetCancelSet() bool { + if x != nil { + return x.CancelSet + } + return false +} + var File_invoicesrpc_invoices_proto protoreflect.FileDescriptor var file_invoicesrpc_invoices_proto_rawDesc = []byte{ @@ -678,41 +903,89 @@ var file_invoicesrpc_invoices_proto_rawDesc = []byte{ 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, - 0x65, 0x66, 0x2a, 0x44, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, - 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, - 0x4c, 0x59, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, - 0x5f, 0x42, 0x4c, 0x41, 0x4e, 0x4b, 0x10, 0x02, 0x32, 0x9b, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, - 0x2a, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, - 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, - 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, - 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, - 0x0e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, - 0x22, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, - 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x42, 0x33, 0x5a, 0x31, 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, 0x2f, - 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x65, 0x66, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, + 0x49, 0x64, 0x22, 0xcd, 0x03, 0x0a, 0x11, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x65, 0x78, 0x69, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x22, + 0x0a, 0x0d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x61, 0x6d, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x41, + 0x6d, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x78, + 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x7f, 0x0a, 0x1d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, + 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x69, 0x74, 0x48, + 0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x19, 0x65, 0x78, 0x69, 0x74, 0x48, + 0x74, 0x6c, 0x63, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4c, 0x0a, 0x1e, 0x45, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, + 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x9a, 0x01, 0x0a, 0x12, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0b, 0x63, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, + 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x5f, 0x73, 0x65, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, + 0x65, 0x74, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x2a, + 0x44, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x11, + 0x0a, 0x0d, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, + 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x42, 0x4c, + 0x41, 0x4e, 0x4b, 0x10, 0x02, 0x32, 0xf0, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, + 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0d, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, 0x0e, 0x41, 0x64, + 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, + 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, + 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x40, 0x0a, 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x4d, 0x73, 0x67, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, + 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x42, 0x33, 0x5a, 0x31, 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, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -728,7 +1001,7 @@ func file_invoicesrpc_invoices_proto_rawDescGZIP() []byte { } var file_invoicesrpc_invoices_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_invoicesrpc_invoices_proto_goTypes = []interface{}{ (LookupModifier)(0), // 0: invoicesrpc.LookupModifier (*CancelInvoiceMsg)(nil), // 1: invoicesrpc.CancelInvoiceMsg @@ -739,27 +1012,37 @@ var file_invoicesrpc_invoices_proto_goTypes = []interface{}{ (*SettleInvoiceResp)(nil), // 6: invoicesrpc.SettleInvoiceResp (*SubscribeSingleInvoiceRequest)(nil), // 7: invoicesrpc.SubscribeSingleInvoiceRequest (*LookupInvoiceMsg)(nil), // 8: invoicesrpc.LookupInvoiceMsg - (*lnrpc.RouteHint)(nil), // 9: lnrpc.RouteHint - (*lnrpc.Invoice)(nil), // 10: lnrpc.Invoice + (*CircuitKey)(nil), // 9: invoicesrpc.CircuitKey + (*HtlcModifyRequest)(nil), // 10: invoicesrpc.HtlcModifyRequest + (*HtlcModifyResponse)(nil), // 11: invoicesrpc.HtlcModifyResponse + nil, // 12: invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntry + (*lnrpc.RouteHint)(nil), // 13: lnrpc.RouteHint + (*lnrpc.Invoice)(nil), // 14: lnrpc.Invoice } var file_invoicesrpc_invoices_proto_depIdxs = []int32{ - 9, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint + 13, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint 0, // 1: invoicesrpc.LookupInvoiceMsg.lookup_modifier:type_name -> invoicesrpc.LookupModifier - 7, // 2: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest - 1, // 3: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg - 3, // 4: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest - 5, // 5: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg - 8, // 6: invoicesrpc.Invoices.LookupInvoiceV2:input_type -> invoicesrpc.LookupInvoiceMsg - 10, // 7: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice - 2, // 8: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp - 4, // 9: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp - 6, // 10: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp - 10, // 11: invoicesrpc.Invoices.LookupInvoiceV2:output_type -> lnrpc.Invoice - 7, // [7:12] is the sub-list for method output_type - 2, // [2:7] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 14, // 2: invoicesrpc.HtlcModifyRequest.invoice:type_name -> lnrpc.Invoice + 9, // 3: invoicesrpc.HtlcModifyRequest.exit_htlc_circuit_key:type_name -> invoicesrpc.CircuitKey + 12, // 4: invoicesrpc.HtlcModifyRequest.exit_htlc_wire_custom_records:type_name -> invoicesrpc.HtlcModifyRequest.ExitHtlcWireCustomRecordsEntry + 9, // 5: invoicesrpc.HtlcModifyResponse.circuit_key:type_name -> invoicesrpc.CircuitKey + 7, // 6: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest + 1, // 7: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg + 3, // 8: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest + 5, // 9: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg + 8, // 10: invoicesrpc.Invoices.LookupInvoiceV2:input_type -> invoicesrpc.LookupInvoiceMsg + 11, // 11: invoicesrpc.Invoices.HtlcModifier:input_type -> invoicesrpc.HtlcModifyResponse + 14, // 12: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice + 2, // 13: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp + 4, // 14: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp + 6, // 15: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp + 14, // 16: invoicesrpc.Invoices.LookupInvoiceV2:output_type -> lnrpc.Invoice + 10, // 17: invoicesrpc.Invoices.HtlcModifier:output_type -> invoicesrpc.HtlcModifyRequest + 12, // [12:18] is the sub-list for method output_type + 6, // [6:12] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_invoicesrpc_invoices_proto_init() } @@ -864,19 +1147,56 @@ func file_invoicesrpc_invoices_proto_init() { return nil } } + file_invoicesrpc_invoices_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CircuitKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcModifyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcModifyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_invoicesrpc_invoices_proto_msgTypes[7].OneofWrappers = []interface{}{ (*LookupInvoiceMsg_PaymentHash)(nil), (*LookupInvoiceMsg_PaymentAddr)(nil), (*LookupInvoiceMsg_SetId)(nil), } + file_invoicesrpc_invoices_proto_msgTypes[10].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_invoicesrpc_invoices_proto_rawDesc, NumEnums: 1, - NumMessages: 8, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/invoicesrpc/invoices.pb.gw.go b/lnrpc/invoicesrpc/invoices.pb.gw.go index 575f4849eb..530f2a347a 100644 --- a/lnrpc/invoicesrpc/invoices.pb.gw.go +++ b/lnrpc/invoicesrpc/invoices.pb.gw.go @@ -203,6 +203,58 @@ func local_request_Invoices_LookupInvoiceV2_0(ctx context.Context, marshaler run } +func request_Invoices_HtlcModifier_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (Invoices_HtlcModifierClient, runtime.ServerMetadata, error) { + var metadata runtime.ServerMetadata + stream, err := client.HtlcModifier(ctx) + if err != nil { + grpclog.Infof("Failed to start streaming: %v", err) + return nil, metadata, err + } + dec := marshaler.NewDecoder(req.Body) + handleSend := func() error { + var protoReq HtlcModifyResponse + err := dec.Decode(&protoReq) + if err == io.EOF { + return err + } + if err != nil { + grpclog.Infof("Failed to decode request: %v", err) + return err + } + if err := stream.Send(&protoReq); err != nil { + grpclog.Infof("Failed to send request: %v", err) + return err + } + return nil + } + if err := handleSend(); err != nil { + if cerr := stream.CloseSend(); cerr != nil { + grpclog.Infof("Failed to terminate client stream: %v", cerr) + } + if err == io.EOF { + return stream, metadata, nil + } + return nil, metadata, err + } + go func() { + for { + if err := handleSend(); err != nil { + break + } + } + if err := stream.CloseSend(); err != nil { + grpclog.Infof("Failed to terminate client stream: %v", err) + } + }() + header, err := stream.Header() + if err != nil { + grpclog.Infof("Failed to get header from client: %v", err) + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil +} + // RegisterInvoicesHandlerServer registers the http handlers for service Invoices to "mux". // UnaryRPC :call InvoicesServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -308,6 +360,13 @@ func RegisterInvoicesHandlerServer(ctx context.Context, mux *runtime.ServeMux, s }) + mux.Handle("POST", pattern_Invoices_HtlcModifier_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + return nil } @@ -449,6 +508,26 @@ func RegisterInvoicesHandlerClient(ctx context.Context, mux *runtime.ServeMux, c }) + mux.Handle("POST", pattern_Invoices_HtlcModifier_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/HtlcModifier", runtime.WithHTTPPathPattern("/v2/invoices/htlcmodifier")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Invoices_HtlcModifier_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Invoices_HtlcModifier_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -462,6 +541,8 @@ var ( pattern_Invoices_SettleInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "settle"}, "")) pattern_Invoices_LookupInvoiceV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "lookup"}, "")) + + pattern_Invoices_HtlcModifier_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "htlcmodifier"}, "")) ) var ( @@ -474,4 +555,6 @@ var ( forward_Invoices_SettleInvoice_0 = runtime.ForwardResponseMessage forward_Invoices_LookupInvoiceV2_0 = runtime.ForwardResponseMessage + + forward_Invoices_HtlcModifier_0 = runtime.ForwardResponseStream ) diff --git a/lnrpc/invoicesrpc/invoices.proto b/lnrpc/invoicesrpc/invoices.proto index 7afffba4e3..d9d3bc1236 100644 --- a/lnrpc/invoicesrpc/invoices.proto +++ b/lnrpc/invoicesrpc/invoices.proto @@ -55,10 +55,19 @@ service Invoices { rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp); /* - LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced + LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced using either its payment hash, payment address, or set ID. */ rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice); + + /* + HtlcModifier is a bidirectional streaming RPC that allows a client to + intercept and modify the HTLCs that attempt to settle the given invoice. The + server will send HTLCs of invoices to the client and the client can modify + some aspects of the HTLC in order to pass the invoice acceptance tests. + */ + rpc HtlcModifier (stream HtlcModifyResponse) + returns (stream HtlcModifyRequest); } message CancelInvoiceMsg { @@ -192,3 +201,51 @@ message LookupInvoiceMsg { LookupModifier lookup_modifier = 4; } + +// CircuitKey is a unique identifier for an HTLC. +message CircuitKey { + // The id of the channel that the is part of this circuit. + uint64 chan_id = 1; + + // The index of the incoming htlc in the incoming channel. + uint64 htlc_id = 2; +} + +message HtlcModifyRequest { + // The invoice the intercepted HTLC is attempting to settle. The HTLCs in + // the invoice are only HTLCs that have already been accepted or settled, + // not including the current intercepted HTLC. + lnrpc.Invoice invoice = 1; + + // The unique identifier of the HTLC of this intercepted HTLC. + CircuitKey exit_htlc_circuit_key = 2; + + // The amount in milli-satoshi that the exit HTLC is attempting to pay. + uint64 exit_htlc_amt = 3; + + // The absolute expiry height of the exit HTLC. + uint32 exit_htlc_expiry = 4; + + // The current block height. + uint32 current_height = 5; + + // The wire message custom records of the exit HTLC. + map exit_htlc_wire_custom_records = 6; +} + +message HtlcModifyResponse { + // The circuit key of the HTLC that the client wants to modify. + CircuitKey circuit_key = 1; + + // The modified amount in milli-satoshi that the exit HTLC is paying. This + // value can be different from the actual on-chain HTLC amount, in case the + // HTLC carries other valuable items, as can be the case with custom channel + // types. + optional uint64 amt_paid = 2; + + // This flag indicates whether the HTLCs associated with the invoices should + // be cancelled. The interceptor client may set this field if some + // unexpected behavior is encountered. Setting this will ignore the amt_paid + // field. + bool cancel_set = 3; +} diff --git a/lnrpc/invoicesrpc/invoices.swagger.json b/lnrpc/invoicesrpc/invoices.swagger.json index 43fdf2b87d..fba415fb36 100644 --- a/lnrpc/invoicesrpc/invoices.swagger.json +++ b/lnrpc/invoicesrpc/invoices.swagger.json @@ -82,9 +82,52 @@ ] } }, + "/v2/invoices/htlcmodifier": { + "post": { + "summary": "HtlcModifier is a bidirectional streaming RPC that allows a client to\nintercept and modify the HTLCs that attempt to settle the given invoice. The\nserver will send HTLCs of invoices to the client and the client can modify\nsome aspects of the HTLC in order to pass the invoice acceptance tests.", + "operationId": "Invoices_HtlcModifier", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/invoicesrpcHtlcModifyRequest" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of invoicesrpcHtlcModifyRequest" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": " (streaming inputs)", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/invoicesrpcHtlcModifyResponse" + } + } + ], + "tags": [ + "Invoices" + ] + } + }, "/v2/invoices/lookup": { "get": { - "summary": "LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced\nusing either its payment hash, payment address, or set ID.", + "summary": "LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced\nusing either its payment hash, payment address, or set ID.", "operationId": "Invoices_LookupInvoiceV2", "responses": { "200": { @@ -317,6 +360,76 @@ "invoicesrpcCancelInvoiceResp": { "type": "object" }, + "invoicesrpcCircuitKey": { + "type": "object", + "properties": { + "chan_id": { + "type": "string", + "format": "uint64", + "description": "The id of the channel that the is part of this circuit." + }, + "htlc_id": { + "type": "string", + "format": "uint64", + "description": "The index of the incoming htlc in the incoming channel." + } + }, + "description": "CircuitKey is a unique identifier for an HTLC." + }, + "invoicesrpcHtlcModifyRequest": { + "type": "object", + "properties": { + "invoice": { + "$ref": "#/definitions/lnrpcInvoice", + "description": "The invoice the intercepted HTLC is attempting to settle. The HTLCs in\nthe invoice are only HTLCs that have already been accepted or settled,\nnot including the current intercepted HTLC." + }, + "exit_htlc_circuit_key": { + "$ref": "#/definitions/invoicesrpcCircuitKey", + "description": "The unique identifier of the HTLC of this intercepted HTLC." + }, + "exit_htlc_amt": { + "type": "string", + "format": "uint64", + "description": "The amount in milli-satoshi that the exit HTLC is attempting to pay." + }, + "exit_htlc_expiry": { + "type": "integer", + "format": "int64", + "description": "The absolute expiry height of the exit HTLC." + }, + "current_height": { + "type": "integer", + "format": "int64", + "description": "The current block height." + }, + "exit_htlc_wire_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "The wire message custom records of the exit HTLC." + } + } + }, + "invoicesrpcHtlcModifyResponse": { + "type": "object", + "properties": { + "circuit_key": { + "$ref": "#/definitions/invoicesrpcCircuitKey", + "description": "The circuit key of the HTLC that the client wants to modify." + }, + "amt_paid": { + "type": "string", + "format": "uint64", + "description": "The modified amount in milli-satoshi that the exit HTLC is paying. This\nvalue can be different from the actual on-chain HTLC amount, in case the\nHTLC carries other valuable items, as can be the case with custom channel\ntypes." + }, + "cancel_set": { + "type": "boolean", + "description": "This flag indicates whether the HTLCs associated with the invoices should\nbe cancelled. The interceptor client may set this field if some\nunexpected behavior is encountered. Setting this will ignore the amt_paid\nfield." + } + } + }, "invoicesrpcLookupModifier": { "type": "string", "enum": [ @@ -675,6 +788,11 @@ "amp": { "$ref": "#/definitions/lnrpcAMP", "description": "Details relevant to AMP HTLCs, only populated if this is an AMP HTLC." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } }, "title": "Details of an HTLC that paid to an invoice" diff --git a/lnrpc/invoicesrpc/invoices.yaml b/lnrpc/invoicesrpc/invoices.yaml index ef62d7f031..66d53153e9 100644 --- a/lnrpc/invoicesrpc/invoices.yaml +++ b/lnrpc/invoicesrpc/invoices.yaml @@ -16,3 +16,6 @@ http: body: "*" - selector: invoicesrpc.Invoices.LookupInvoiceV2 get: "/v2/invoices/lookup" + - selector: invoicesrpc.Invoices.HtlcModifier + post: "/v2/invoices/htlcmodifier" + body: "*" \ No newline at end of file diff --git a/lnrpc/invoicesrpc/invoices_grpc.pb.go b/lnrpc/invoicesrpc/invoices_grpc.pb.go index bc2f24ac10..a8e6d187e9 100644 --- a/lnrpc/invoicesrpc/invoices_grpc.pb.go +++ b/lnrpc/invoicesrpc/invoices_grpc.pb.go @@ -36,9 +36,14 @@ type InvoicesClient interface { // SettleInvoice settles an accepted invoice. If the invoice is already // settled, this call will succeed. SettleInvoice(ctx context.Context, in *SettleInvoiceMsg, opts ...grpc.CallOption) (*SettleInvoiceResp, error) - // LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced + // LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced // using either its payment hash, payment address, or set ID. LookupInvoiceV2(ctx context.Context, in *LookupInvoiceMsg, opts ...grpc.CallOption) (*lnrpc.Invoice, error) + // HtlcModifier is a bidirectional streaming RPC that allows a client to + // intercept and modify the HTLCs that attempt to settle the given invoice. The + // server will send HTLCs of invoices to the client and the client can modify + // some aspects of the HTLC in order to pass the invoice acceptance tests. + HtlcModifier(ctx context.Context, opts ...grpc.CallOption) (Invoices_HtlcModifierClient, error) } type invoicesClient struct { @@ -117,6 +122,37 @@ func (c *invoicesClient) LookupInvoiceV2(ctx context.Context, in *LookupInvoiceM return out, nil } +func (c *invoicesClient) HtlcModifier(ctx context.Context, opts ...grpc.CallOption) (Invoices_HtlcModifierClient, error) { + stream, err := c.cc.NewStream(ctx, &Invoices_ServiceDesc.Streams[1], "/invoicesrpc.Invoices/HtlcModifier", opts...) + if err != nil { + return nil, err + } + x := &invoicesHtlcModifierClient{stream} + return x, nil +} + +type Invoices_HtlcModifierClient interface { + Send(*HtlcModifyResponse) error + Recv() (*HtlcModifyRequest, error) + grpc.ClientStream +} + +type invoicesHtlcModifierClient struct { + grpc.ClientStream +} + +func (x *invoicesHtlcModifierClient) Send(m *HtlcModifyResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *invoicesHtlcModifierClient) Recv() (*HtlcModifyRequest, error) { + m := new(HtlcModifyRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // InvoicesServer is the server API for Invoices service. // All implementations must embed UnimplementedInvoicesServer // for forward compatibility @@ -138,9 +174,14 @@ type InvoicesServer interface { // SettleInvoice settles an accepted invoice. If the invoice is already // settled, this call will succeed. SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error) - // LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced + // LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced // using either its payment hash, payment address, or set ID. LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) + // HtlcModifier is a bidirectional streaming RPC that allows a client to + // intercept and modify the HTLCs that attempt to settle the given invoice. The + // server will send HTLCs of invoices to the client and the client can modify + // some aspects of the HTLC in order to pass the invoice acceptance tests. + HtlcModifier(Invoices_HtlcModifierServer) error mustEmbedUnimplementedInvoicesServer() } @@ -163,6 +204,9 @@ func (UnimplementedInvoicesServer) SettleInvoice(context.Context, *SettleInvoice func (UnimplementedInvoicesServer) LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) { return nil, status.Errorf(codes.Unimplemented, "method LookupInvoiceV2 not implemented") } +func (UnimplementedInvoicesServer) HtlcModifier(Invoices_HtlcModifierServer) error { + return status.Errorf(codes.Unimplemented, "method HtlcModifier not implemented") +} func (UnimplementedInvoicesServer) mustEmbedUnimplementedInvoicesServer() {} // UnsafeInvoicesServer may be embedded to opt out of forward compatibility for this service. @@ -269,6 +313,32 @@ func _Invoices_LookupInvoiceV2_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Invoices_HtlcModifier_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(InvoicesServer).HtlcModifier(&invoicesHtlcModifierServer{stream}) +} + +type Invoices_HtlcModifierServer interface { + Send(*HtlcModifyRequest) error + Recv() (*HtlcModifyResponse, error) + grpc.ServerStream +} + +type invoicesHtlcModifierServer struct { + grpc.ServerStream +} + +func (x *invoicesHtlcModifierServer) Send(m *HtlcModifyRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *invoicesHtlcModifierServer) Recv() (*HtlcModifyResponse, error) { + m := new(HtlcModifyResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // Invoices_ServiceDesc is the grpc.ServiceDesc for Invoices service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -299,6 +369,12 @@ var Invoices_ServiceDesc = grpc.ServiceDesc{ Handler: _Invoices_SubscribeSingleInvoice_Handler, ServerStreams: true, }, + { + StreamName: "HtlcModifier", + Handler: _Invoices_HtlcModifier_Handler, + ServerStreams: true, + ClientStreams: true, + }, }, Metadata: "invoicesrpc/invoices.proto", } diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index 23b8722caf..8ca02b260d 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -30,6 +30,9 @@ const ( ) var ( + // ErrServerShuttingDown is returned when the server is shutting down. + ErrServerShuttingDown = errors.New("server shutting down") + // macaroonOps are the set of capabilities that our minted macaroon (if // it doesn't already exist) will have. macaroonOps = []bakery.Op{ @@ -65,6 +68,10 @@ var ( Entity: "invoices", Action: "write", }}, + "/invoicesrpc.Invoices/HtlcModifier": {{ + Entity: "invoices", + Action: "write", + }}, } // DefaultInvoicesMacFilename is the default name of the invoices @@ -215,8 +222,9 @@ func (r *ServerShell) RegisterWithRestServer(ctx context.Context, // methods routed towards it. // // NOTE: This is part of the lnrpc.GrpcHandler interface. -func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( - lnrpc.SubServer, lnrpc.MacaroonPerms, error) { +func (r *ServerShell) CreateSubServer( + configRegistry lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, + lnrpc.MacaroonPerms, error) { subServer, macPermissions, err := createNewSubServer(configRegistry) if err != nil { @@ -257,6 +265,14 @@ func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest, return err } + // Give the aux data parser a chance to format the + // custom data in the invoice HTLCs. + err = s.cfg.ParseAuxData(rpcInvoice) + if err != nil { + return fmt.Errorf("error parsing custom data: "+ + "%w", err) + } + if err := updateStream.Send(rpcInvoice); err != nil { return err } @@ -444,5 +460,50 @@ func (s *Server) LookupInvoiceV2(ctx context.Context, return nil, err } - return CreateRPCInvoice(&invoice, s.cfg.ChainParams) + rpcInvoice, err := CreateRPCInvoice(&invoice, s.cfg.ChainParams) + if err != nil { + return nil, err + } + + // Give the aux data parser a chance to format the custom data in the + // invoice HTLCs. + err = s.cfg.ParseAuxData(rpcInvoice) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", err) + } + + return rpcInvoice, nil +} + +// HtlcModifier is a bidirectional streaming RPC that allows a client to +// intercept and modify the HTLCs that attempt to settle the given invoice. The +// server will send HTLCs of invoices to the client and the client can modify +// some aspects of the HTLC in order to pass the invoice acceptance tests. +func (s *Server) HtlcModifier( + modifierServer Invoices_HtlcModifierServer) error { + + modifier := newHtlcModifier(s.cfg.ChainParams, modifierServer) + reset, modifierQuit, err := s.cfg.HtlcModifier.RegisterInterceptor( + modifier.onIntercept, + ) + if err != nil { + return fmt.Errorf("cannot register interceptor: %w", err) + } + + defer reset() + + log.Debugf("Invoice HTLC modifier client connected") + + for { + select { + case <-modifierServer.Context().Done(): + return modifierServer.Context().Err() + + case <-modifierQuit: + return ErrServerShuttingDown + + case <-s.quit: + return ErrServerShuttingDown + } + } } diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index ce02769fb4..955ba6acf2 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -123,6 +123,16 @@ func CreateRPCInvoice(invoice *invoices.Invoice, MppTotalAmtMsat: uint64(htlc.MppTotalAmt), } + // The custom channel data is currently just the raw bytes of + // the encoded custom records. + customData, err := lnwire.CustomRecords( + htlc.CustomRecords, + ).Serialize() + if err != nil { + return nil, err + } + rpcHtlc.CustomChannelData = customData + // Populate any fields relevant to AMP payments. if htlc.AMP != nil { rootShare := htlc.AMP.Record.RootShare() diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 285dbdce9c..60a970ba61 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, } ) @@ -1017,7 +1024,7 @@ func (x PendingChannelsResponse_ForceClosedChannel_AnchorState) Number() protore // Deprecated: Use PendingChannelsResponse_ForceClosedChannel_AnchorState.Descriptor instead. func (PendingChannelsResponse_ForceClosedChannel_AnchorState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 5, 0} + return file_lightning_proto_rawDescGZIP(), []int{90, 5, 0} } type ChannelEventUpdate_UpdateType int32 @@ -1075,7 +1082,7 @@ func (x ChannelEventUpdate_UpdateType) Number() protoreflect.EnumNumber { // Deprecated: Use ChannelEventUpdate_UpdateType.Descriptor instead. func (ChannelEventUpdate_UpdateType) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{91, 0} + return file_lightning_proto_rawDescGZIP(), []int{92, 0} } type Invoice_InvoiceState int32 @@ -1127,7 +1134,7 @@ func (x Invoice_InvoiceState) Number() protoreflect.EnumNumber { // Deprecated: Use Invoice_InvoiceState.Descriptor instead. func (Invoice_InvoiceState) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{134, 0} + return file_lightning_proto_rawDescGZIP(), []int{135, 0} } type Payment_PaymentStatus int32 @@ -1189,7 +1196,7 @@ func (x Payment_PaymentStatus) Number() protoreflect.EnumNumber { // Deprecated: Use Payment_PaymentStatus.Descriptor instead. func (Payment_PaymentStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{143, 0} + return file_lightning_proto_rawDescGZIP(), []int{144, 0} } type HTLCAttempt_HTLCStatus int32 @@ -1238,7 +1245,7 @@ func (x HTLCAttempt_HTLCStatus) Number() protoreflect.EnumNumber { // Deprecated: Use HTLCAttempt_HTLCStatus.Descriptor instead. func (HTLCAttempt_HTLCStatus) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{144, 0} + return file_lightning_proto_rawDescGZIP(), []int{145, 0} } type Failure_FailureCode int32 @@ -1372,7 +1379,7 @@ func (x Failure_FailureCode) Number() protoreflect.EnumNumber { // Deprecated: Use Failure_FailureCode.Descriptor instead. func (Failure_FailureCode) EnumDescriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{188, 0} + return file_lightning_proto_rawDescGZIP(), []int{189, 0} } type LookupHtlcResolutionRequest struct { @@ -4703,6 +4710,8 @@ type Channel struct { // useful information. This is only ever stored locally and in no way impacts // the channel's operation. Memo string `protobuf:"bytes,36,opt,name=memo,proto3" json:"memo,omitempty"` + // Custom channel data that might be populated in custom channels. + CustomChannelData []byte `protobuf:"bytes,37,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` } func (x *Channel) Reset() { @@ -4993,6 +5002,13 @@ func (x *Channel) GetMemo() string { return "" } +func (x *Channel) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + type ListChannelsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6729,6 +6745,84 @@ func (x *ChannelOpenUpdate) GetChannelPoint() *ChannelPoint { return nil } +type CloseOutput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The amount in satoshi of this close output. This amount is the final + // commitment balance of the channel and the actual amount paid out on chain + // might be smaller due to subtracted fees. + AmountSat int64 `protobuf:"varint,1,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"` + // The pkScript of the close output. + PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` + // Whether this output is for the local or remote node. + IsLocal bool `protobuf:"varint,3,opt,name=is_local,json=isLocal,proto3" json:"is_local,omitempty"` + // The TLV encoded custom channel data records for this output, which might + // be set for custom channels. + CustomChannelData []byte `protobuf:"bytes,4,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` +} + +func (x *CloseOutput) Reset() { + *x = CloseOutput{} + if protoimpl.UnsafeEnabled { + mi := &file_lightning_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseOutput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseOutput) ProtoMessage() {} + +func (x *CloseOutput) ProtoReflect() protoreflect.Message { + mi := &file_lightning_proto_msgTypes[66] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseOutput.ProtoReflect.Descriptor instead. +func (*CloseOutput) Descriptor() ([]byte, []int) { + return file_lightning_proto_rawDescGZIP(), []int{66} +} + +func (x *CloseOutput) GetAmountSat() int64 { + if x != nil { + return x.AmountSat + } + return 0 +} + +func (x *CloseOutput) GetPkScript() []byte { + if x != nil { + return x.PkScript + } + return nil +} + +func (x *CloseOutput) GetIsLocal() bool { + if x != nil { + return x.IsLocal + } + return false +} + +func (x *CloseOutput) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + type ChannelCloseUpdate struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6736,12 +6830,20 @@ type ChannelCloseUpdate struct { ClosingTxid []byte `protobuf:"bytes,1,opt,name=closing_txid,json=closingTxid,proto3" json:"closing_txid,omitempty"` Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` + // The local channel close output. If the local channel balance was dust to + // begin with, this output will not be set. + LocalCloseOutput *CloseOutput `protobuf:"bytes,3,opt,name=local_close_output,json=localCloseOutput,proto3" json:"local_close_output,omitempty"` + // The remote channel close output. If the remote channel balance was dust + // to begin with, this output will not be set. + RemoteCloseOutput *CloseOutput `protobuf:"bytes,4,opt,name=remote_close_output,json=remoteCloseOutput,proto3" json:"remote_close_output,omitempty"` + // Any additional outputs that might be added for custom channel types. + AdditionalOutputs []*CloseOutput `protobuf:"bytes,5,rep,name=additional_outputs,json=additionalOutputs,proto3" json:"additional_outputs,omitempty"` } func (x *ChannelCloseUpdate) Reset() { *x = ChannelCloseUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[66] + mi := &file_lightning_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6754,7 +6856,7 @@ func (x *ChannelCloseUpdate) String() string { func (*ChannelCloseUpdate) ProtoMessage() {} func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[66] + mi := &file_lightning_proto_msgTypes[67] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6767,7 +6869,7 @@ func (x *ChannelCloseUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelCloseUpdate.ProtoReflect.Descriptor instead. func (*ChannelCloseUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{66} + return file_lightning_proto_rawDescGZIP(), []int{67} } func (x *ChannelCloseUpdate) GetClosingTxid() []byte { @@ -6784,6 +6886,27 @@ func (x *ChannelCloseUpdate) GetSuccess() bool { return false } +func (x *ChannelCloseUpdate) GetLocalCloseOutput() *CloseOutput { + if x != nil { + return x.LocalCloseOutput + } + return nil +} + +func (x *ChannelCloseUpdate) GetRemoteCloseOutput() *CloseOutput { + if x != nil { + return x.RemoteCloseOutput + } + return nil +} + +func (x *ChannelCloseUpdate) GetAdditionalOutputs() []*CloseOutput { + if x != nil { + return x.AdditionalOutputs + } + return nil +} + type CloseChannelRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6826,7 +6949,7 @@ type CloseChannelRequest struct { func (x *CloseChannelRequest) Reset() { *x = CloseChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[67] + mi := &file_lightning_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6839,7 +6962,7 @@ func (x *CloseChannelRequest) String() string { func (*CloseChannelRequest) ProtoMessage() {} func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[67] + mi := &file_lightning_proto_msgTypes[68] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6852,7 +6975,7 @@ func (x *CloseChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseChannelRequest.ProtoReflect.Descriptor instead. func (*CloseChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{67} + return file_lightning_proto_rawDescGZIP(), []int{68} } func (x *CloseChannelRequest) GetChannelPoint() *ChannelPoint { @@ -6928,7 +7051,7 @@ type CloseStatusUpdate struct { func (x *CloseStatusUpdate) Reset() { *x = CloseStatusUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[68] + mi := &file_lightning_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6941,7 +7064,7 @@ func (x *CloseStatusUpdate) String() string { func (*CloseStatusUpdate) ProtoMessage() {} func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[68] + mi := &file_lightning_proto_msgTypes[69] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6954,7 +7077,7 @@ func (x *CloseStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use CloseStatusUpdate.ProtoReflect.Descriptor instead. func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{68} + return file_lightning_proto_rawDescGZIP(), []int{69} } func (m *CloseStatusUpdate) GetUpdate() isCloseStatusUpdate_Update { @@ -7019,7 +7142,7 @@ type PendingUpdate struct { func (x *PendingUpdate) Reset() { *x = PendingUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[69] + mi := &file_lightning_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7032,7 +7155,7 @@ func (x *PendingUpdate) String() string { func (*PendingUpdate) ProtoMessage() {} func (x *PendingUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[69] + mi := &file_lightning_proto_msgTypes[70] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7045,7 +7168,7 @@ func (x *PendingUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingUpdate.ProtoReflect.Descriptor instead. func (*PendingUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{69} + return file_lightning_proto_rawDescGZIP(), []int{70} } func (x *PendingUpdate) GetTxid() []byte { @@ -7071,7 +7194,7 @@ type InstantUpdate struct { func (x *InstantUpdate) Reset() { *x = InstantUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[70] + mi := &file_lightning_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7084,7 +7207,7 @@ func (x *InstantUpdate) String() string { func (*InstantUpdate) ProtoMessage() {} func (x *InstantUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[70] + mi := &file_lightning_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7097,7 +7220,7 @@ func (x *InstantUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantUpdate.ProtoReflect.Descriptor instead. func (*InstantUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{70} + return file_lightning_proto_rawDescGZIP(), []int{71} } type ReadyForPsbtFunding struct { @@ -7121,7 +7244,7 @@ type ReadyForPsbtFunding struct { func (x *ReadyForPsbtFunding) Reset() { *x = ReadyForPsbtFunding{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[71] + mi := &file_lightning_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7134,7 +7257,7 @@ func (x *ReadyForPsbtFunding) String() string { func (*ReadyForPsbtFunding) ProtoMessage() {} func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[71] + mi := &file_lightning_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7147,7 +7270,7 @@ func (x *ReadyForPsbtFunding) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadyForPsbtFunding.ProtoReflect.Descriptor instead. func (*ReadyForPsbtFunding) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{71} + return file_lightning_proto_rawDescGZIP(), []int{72} } func (x *ReadyForPsbtFunding) GetFundingAddress() string { @@ -7199,7 +7322,7 @@ type BatchOpenChannelRequest struct { func (x *BatchOpenChannelRequest) Reset() { *x = BatchOpenChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[72] + mi := &file_lightning_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7212,7 +7335,7 @@ func (x *BatchOpenChannelRequest) String() string { func (*BatchOpenChannelRequest) ProtoMessage() {} func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[72] + mi := &file_lightning_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7225,7 +7348,7 @@ func (x *BatchOpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelRequest.ProtoReflect.Descriptor instead. func (*BatchOpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{72} + return file_lightning_proto_rawDescGZIP(), []int{73} } func (x *BatchOpenChannelRequest) GetChannels() []*BatchOpenChannel { @@ -7357,7 +7480,7 @@ type BatchOpenChannel struct { func (x *BatchOpenChannel) Reset() { *x = BatchOpenChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[73] + mi := &file_lightning_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7370,7 +7493,7 @@ func (x *BatchOpenChannel) String() string { func (*BatchOpenChannel) ProtoMessage() {} func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[73] + mi := &file_lightning_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7383,7 +7506,7 @@ func (x *BatchOpenChannel) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannel.ProtoReflect.Descriptor instead. func (*BatchOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{73} + return file_lightning_proto_rawDescGZIP(), []int{74} } func (x *BatchOpenChannel) GetNodePubkey() []byte { @@ -7537,7 +7660,7 @@ type BatchOpenChannelResponse struct { func (x *BatchOpenChannelResponse) Reset() { *x = BatchOpenChannelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[74] + mi := &file_lightning_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7550,7 +7673,7 @@ func (x *BatchOpenChannelResponse) String() string { func (*BatchOpenChannelResponse) ProtoMessage() {} func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[74] + mi := &file_lightning_proto_msgTypes[75] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7563,7 +7686,7 @@ func (x *BatchOpenChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchOpenChannelResponse.ProtoReflect.Descriptor instead. func (*BatchOpenChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{74} + return file_lightning_proto_rawDescGZIP(), []int{75} } func (x *BatchOpenChannelResponse) GetPendingChannels() []*PendingUpdate { @@ -7684,7 +7807,7 @@ type OpenChannelRequest struct { func (x *OpenChannelRequest) Reset() { *x = OpenChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[75] + mi := &file_lightning_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7697,7 +7820,7 @@ func (x *OpenChannelRequest) String() string { func (*OpenChannelRequest) ProtoMessage() {} func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[75] + mi := &file_lightning_proto_msgTypes[76] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7710,7 +7833,7 @@ func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead. func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{75} + return file_lightning_proto_rawDescGZIP(), []int{76} } func (x *OpenChannelRequest) GetSatPerVbyte() uint64 { @@ -7930,7 +8053,7 @@ type OpenStatusUpdate struct { func (x *OpenStatusUpdate) Reset() { *x = OpenStatusUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[76] + mi := &file_lightning_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7943,7 +8066,7 @@ func (x *OpenStatusUpdate) String() string { func (*OpenStatusUpdate) ProtoMessage() {} func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[76] + mi := &file_lightning_proto_msgTypes[77] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7956,7 +8079,7 @@ func (x *OpenStatusUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenStatusUpdate.ProtoReflect.Descriptor instead. func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{76} + return file_lightning_proto_rawDescGZIP(), []int{77} } func (m *OpenStatusUpdate) GetUpdate() isOpenStatusUpdate_Update { @@ -8036,7 +8159,7 @@ type KeyLocator struct { func (x *KeyLocator) Reset() { *x = KeyLocator{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[77] + mi := &file_lightning_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8049,7 +8172,7 @@ func (x *KeyLocator) String() string { func (*KeyLocator) ProtoMessage() {} func (x *KeyLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[77] + mi := &file_lightning_proto_msgTypes[78] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8062,7 +8185,7 @@ func (x *KeyLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyLocator.ProtoReflect.Descriptor instead. func (*KeyLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{77} + return file_lightning_proto_rawDescGZIP(), []int{78} } func (x *KeyLocator) GetKeyFamily() int32 { @@ -8093,7 +8216,7 @@ type KeyDescriptor struct { func (x *KeyDescriptor) Reset() { *x = KeyDescriptor{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[78] + mi := &file_lightning_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8106,7 +8229,7 @@ func (x *KeyDescriptor) String() string { func (*KeyDescriptor) ProtoMessage() {} func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[78] + mi := &file_lightning_proto_msgTypes[79] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8119,7 +8242,7 @@ func (x *KeyDescriptor) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyDescriptor.ProtoReflect.Descriptor instead. func (*KeyDescriptor) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{78} + return file_lightning_proto_rawDescGZIP(), []int{79} } func (x *KeyDescriptor) GetRawKeyBytes() []byte { @@ -8168,7 +8291,7 @@ type ChanPointShim struct { func (x *ChanPointShim) Reset() { *x = ChanPointShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[79] + mi := &file_lightning_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8181,7 +8304,7 @@ func (x *ChanPointShim) String() string { func (*ChanPointShim) ProtoMessage() {} func (x *ChanPointShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[79] + mi := &file_lightning_proto_msgTypes[80] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8194,7 +8317,7 @@ func (x *ChanPointShim) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanPointShim.ProtoReflect.Descriptor instead. func (*ChanPointShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{79} + return file_lightning_proto_rawDescGZIP(), []int{80} } func (x *ChanPointShim) GetAmt() int64 { @@ -8270,7 +8393,7 @@ type PsbtShim struct { func (x *PsbtShim) Reset() { *x = PsbtShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[80] + mi := &file_lightning_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8283,7 +8406,7 @@ func (x *PsbtShim) String() string { func (*PsbtShim) ProtoMessage() {} func (x *PsbtShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[80] + mi := &file_lightning_proto_msgTypes[81] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8296,7 +8419,7 @@ func (x *PsbtShim) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtShim.ProtoReflect.Descriptor instead. func (*PsbtShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{80} + return file_lightning_proto_rawDescGZIP(), []int{81} } func (x *PsbtShim) GetPendingChanId() []byte { @@ -8335,7 +8458,7 @@ type FundingShim struct { func (x *FundingShim) Reset() { *x = FundingShim{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[81] + mi := &file_lightning_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8348,7 +8471,7 @@ func (x *FundingShim) String() string { func (*FundingShim) ProtoMessage() {} func (x *FundingShim) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[81] + mi := &file_lightning_proto_msgTypes[82] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8361,7 +8484,7 @@ func (x *FundingShim) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShim.ProtoReflect.Descriptor instead. func (*FundingShim) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{81} + return file_lightning_proto_rawDescGZIP(), []int{82} } func (m *FundingShim) GetShim() isFundingShim_Shim { @@ -8417,7 +8540,7 @@ type FundingShimCancel struct { func (x *FundingShimCancel) Reset() { *x = FundingShimCancel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[82] + mi := &file_lightning_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8430,7 +8553,7 @@ func (x *FundingShimCancel) String() string { func (*FundingShimCancel) ProtoMessage() {} func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[82] + mi := &file_lightning_proto_msgTypes[83] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8443,7 +8566,7 @@ func (x *FundingShimCancel) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingShimCancel.ProtoReflect.Descriptor instead. func (*FundingShimCancel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{82} + return file_lightning_proto_rawDescGZIP(), []int{83} } func (x *FundingShimCancel) GetPendingChanId() []byte { @@ -8480,7 +8603,7 @@ type FundingPsbtVerify struct { func (x *FundingPsbtVerify) Reset() { *x = FundingPsbtVerify{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[83] + mi := &file_lightning_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8493,7 +8616,7 @@ func (x *FundingPsbtVerify) String() string { func (*FundingPsbtVerify) ProtoMessage() {} func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[83] + mi := &file_lightning_proto_msgTypes[84] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8506,7 +8629,7 @@ func (x *FundingPsbtVerify) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtVerify.ProtoReflect.Descriptor instead. func (*FundingPsbtVerify) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{83} + return file_lightning_proto_rawDescGZIP(), []int{84} } func (x *FundingPsbtVerify) GetFundedPsbt() []byte { @@ -8550,7 +8673,7 @@ type FundingPsbtFinalize struct { func (x *FundingPsbtFinalize) Reset() { *x = FundingPsbtFinalize{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[84] + mi := &file_lightning_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8563,7 +8686,7 @@ func (x *FundingPsbtFinalize) String() string { func (*FundingPsbtFinalize) ProtoMessage() {} func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[84] + mi := &file_lightning_proto_msgTypes[85] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8576,7 +8699,7 @@ func (x *FundingPsbtFinalize) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingPsbtFinalize.ProtoReflect.Descriptor instead. func (*FundingPsbtFinalize) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{84} + return file_lightning_proto_rawDescGZIP(), []int{85} } func (x *FundingPsbtFinalize) GetSignedPsbt() []byte { @@ -8617,7 +8740,7 @@ type FundingTransitionMsg struct { func (x *FundingTransitionMsg) Reset() { *x = FundingTransitionMsg{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[85] + mi := &file_lightning_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8630,7 +8753,7 @@ func (x *FundingTransitionMsg) String() string { func (*FundingTransitionMsg) ProtoMessage() {} func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[85] + mi := &file_lightning_proto_msgTypes[86] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8643,7 +8766,7 @@ func (x *FundingTransitionMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingTransitionMsg.ProtoReflect.Descriptor instead. func (*FundingTransitionMsg) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{85} + return file_lightning_proto_rawDescGZIP(), []int{86} } func (m *FundingTransitionMsg) GetTrigger() isFundingTransitionMsg_Trigger { @@ -8729,7 +8852,7 @@ type FundingStateStepResp struct { func (x *FundingStateStepResp) Reset() { *x = FundingStateStepResp{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[86] + mi := &file_lightning_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8742,7 +8865,7 @@ func (x *FundingStateStepResp) String() string { func (*FundingStateStepResp) ProtoMessage() {} func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[86] + mi := &file_lightning_proto_msgTypes[87] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8755,7 +8878,7 @@ func (x *FundingStateStepResp) ProtoReflect() protoreflect.Message { // Deprecated: Use FundingStateStepResp.ProtoReflect.Descriptor instead. func (*FundingStateStepResp) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{86} + return file_lightning_proto_rawDescGZIP(), []int{87} } type PendingHTLC struct { @@ -8782,7 +8905,7 @@ type PendingHTLC struct { func (x *PendingHTLC) Reset() { *x = PendingHTLC{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[87] + mi := &file_lightning_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8795,7 +8918,7 @@ func (x *PendingHTLC) String() string { func (*PendingHTLC) ProtoMessage() {} func (x *PendingHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[87] + mi := &file_lightning_proto_msgTypes[88] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8808,7 +8931,7 @@ func (x *PendingHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingHTLC.ProtoReflect.Descriptor instead. func (*PendingHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{87} + return file_lightning_proto_rawDescGZIP(), []int{88} } func (x *PendingHTLC) GetIncoming() bool { @@ -8866,7 +8989,7 @@ type PendingChannelsRequest struct { func (x *PendingChannelsRequest) Reset() { *x = PendingChannelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[88] + mi := &file_lightning_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8879,7 +9002,7 @@ func (x *PendingChannelsRequest) String() string { func (*PendingChannelsRequest) ProtoMessage() {} func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[88] + mi := &file_lightning_proto_msgTypes[89] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8892,7 +9015,7 @@ func (x *PendingChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsRequest.ProtoReflect.Descriptor instead. func (*PendingChannelsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{88} + return file_lightning_proto_rawDescGZIP(), []int{89} } func (x *PendingChannelsRequest) GetIncludeRawTx() bool { @@ -8926,7 +9049,7 @@ type PendingChannelsResponse struct { func (x *PendingChannelsResponse) Reset() { *x = PendingChannelsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[89] + mi := &file_lightning_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8939,7 +9062,7 @@ func (x *PendingChannelsResponse) String() string { func (*PendingChannelsResponse) ProtoMessage() {} func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[89] + mi := &file_lightning_proto_msgTypes[90] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8952,7 +9075,7 @@ func (x *PendingChannelsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingChannelsResponse.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89} + return file_lightning_proto_rawDescGZIP(), []int{90} } func (x *PendingChannelsResponse) GetTotalLimboBalance() int64 { @@ -9000,7 +9123,7 @@ type ChannelEventSubscription struct { func (x *ChannelEventSubscription) Reset() { *x = ChannelEventSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[90] + mi := &file_lightning_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9013,7 +9136,7 @@ func (x *ChannelEventSubscription) String() string { func (*ChannelEventSubscription) ProtoMessage() {} func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[90] + mi := &file_lightning_proto_msgTypes[91] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9026,7 +9149,7 @@ func (x *ChannelEventSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventSubscription.ProtoReflect.Descriptor instead. func (*ChannelEventSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{90} + return file_lightning_proto_rawDescGZIP(), []int{91} } type ChannelEventUpdate struct { @@ -9049,7 +9172,7 @@ type ChannelEventUpdate struct { func (x *ChannelEventUpdate) Reset() { *x = ChannelEventUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[91] + mi := &file_lightning_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9062,7 +9185,7 @@ func (x *ChannelEventUpdate) String() string { func (*ChannelEventUpdate) ProtoMessage() {} func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[91] + mi := &file_lightning_proto_msgTypes[92] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9075,7 +9198,7 @@ func (x *ChannelEventUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEventUpdate.ProtoReflect.Descriptor instead. func (*ChannelEventUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{91} + return file_lightning_proto_rawDescGZIP(), []int{92} } func (m *ChannelEventUpdate) GetChannel() isChannelEventUpdate_Channel { @@ -9188,7 +9311,7 @@ type WalletAccountBalance struct { func (x *WalletAccountBalance) Reset() { *x = WalletAccountBalance{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[92] + mi := &file_lightning_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9201,7 +9324,7 @@ func (x *WalletAccountBalance) String() string { func (*WalletAccountBalance) ProtoMessage() {} func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[92] + mi := &file_lightning_proto_msgTypes[93] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9214,7 +9337,7 @@ func (x *WalletAccountBalance) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletAccountBalance.ProtoReflect.Descriptor instead. func (*WalletAccountBalance) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{92} + return file_lightning_proto_rawDescGZIP(), []int{93} } func (x *WalletAccountBalance) GetConfirmedBalance() int64 { @@ -9248,7 +9371,7 @@ type WalletBalanceRequest struct { func (x *WalletBalanceRequest) Reset() { *x = WalletBalanceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[93] + mi := &file_lightning_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9261,7 +9384,7 @@ func (x *WalletBalanceRequest) String() string { func (*WalletBalanceRequest) ProtoMessage() {} func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[93] + mi := &file_lightning_proto_msgTypes[94] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9274,7 +9397,7 @@ func (x *WalletBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceRequest.ProtoReflect.Descriptor instead. func (*WalletBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{93} + return file_lightning_proto_rawDescGZIP(), []int{94} } func (x *WalletBalanceRequest) GetAccount() string { @@ -9314,7 +9437,7 @@ type WalletBalanceResponse struct { func (x *WalletBalanceResponse) Reset() { *x = WalletBalanceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[94] + mi := &file_lightning_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9327,7 +9450,7 @@ func (x *WalletBalanceResponse) String() string { func (*WalletBalanceResponse) ProtoMessage() {} func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[94] + mi := &file_lightning_proto_msgTypes[95] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9340,7 +9463,7 @@ func (x *WalletBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WalletBalanceResponse.ProtoReflect.Descriptor instead. func (*WalletBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{94} + return file_lightning_proto_rawDescGZIP(), []int{95} } func (x *WalletBalanceResponse) GetTotalBalance() int64 { @@ -9399,7 +9522,7 @@ type Amount struct { func (x *Amount) Reset() { *x = Amount{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[95] + mi := &file_lightning_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9412,7 +9535,7 @@ func (x *Amount) String() string { func (*Amount) ProtoMessage() {} func (x *Amount) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[95] + mi := &file_lightning_proto_msgTypes[96] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9425,7 +9548,7 @@ func (x *Amount) ProtoReflect() protoreflect.Message { // Deprecated: Use Amount.ProtoReflect.Descriptor instead. func (*Amount) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{95} + return file_lightning_proto_rawDescGZIP(), []int{96} } func (x *Amount) GetSat() uint64 { @@ -9451,7 +9574,7 @@ type ChannelBalanceRequest struct { func (x *ChannelBalanceRequest) Reset() { *x = ChannelBalanceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[96] + mi := &file_lightning_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9464,7 +9587,7 @@ func (x *ChannelBalanceRequest) String() string { func (*ChannelBalanceRequest) ProtoMessage() {} func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[96] + mi := &file_lightning_proto_msgTypes[97] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9477,7 +9600,7 @@ func (x *ChannelBalanceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceRequest.ProtoReflect.Descriptor instead. func (*ChannelBalanceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{96} + return file_lightning_proto_rawDescGZIP(), []int{97} } type ChannelBalanceResponse struct { @@ -9505,12 +9628,15 @@ type ChannelBalanceResponse struct { PendingOpenLocalBalance *Amount `protobuf:"bytes,7,opt,name=pending_open_local_balance,json=pendingOpenLocalBalance,proto3" json:"pending_open_local_balance,omitempty"` // Sum of channels pending remote balances. PendingOpenRemoteBalance *Amount `protobuf:"bytes,8,opt,name=pending_open_remote_balance,json=pendingOpenRemoteBalance,proto3" json:"pending_open_remote_balance,omitempty"` + // Custom channel data that might be populated if there are custom channels + // present. + CustomChannelData []byte `protobuf:"bytes,9,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` } func (x *ChannelBalanceResponse) Reset() { *x = ChannelBalanceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[97] + mi := &file_lightning_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9523,7 +9649,7 @@ func (x *ChannelBalanceResponse) String() string { func (*ChannelBalanceResponse) ProtoMessage() {} func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[97] + mi := &file_lightning_proto_msgTypes[98] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9536,7 +9662,7 @@ func (x *ChannelBalanceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBalanceResponse.ProtoReflect.Descriptor instead. func (*ChannelBalanceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{97} + return file_lightning_proto_rawDescGZIP(), []int{98} } // Deprecated: Marked as deprecated in lightning.proto. @@ -9597,6 +9723,13 @@ func (x *ChannelBalanceResponse) GetPendingOpenRemoteBalance() *Amount { return nil } +func (x *ChannelBalanceResponse) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + type QueryRoutesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -9680,7 +9813,7 @@ type QueryRoutesRequest struct { func (x *QueryRoutesRequest) Reset() { *x = QueryRoutesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[98] + mi := &file_lightning_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9693,7 +9826,7 @@ func (x *QueryRoutesRequest) String() string { func (*QueryRoutesRequest) ProtoMessage() {} func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[98] + mi := &file_lightning_proto_msgTypes[99] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9706,7 +9839,7 @@ func (x *QueryRoutesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesRequest.ProtoReflect.Descriptor instead. func (*QueryRoutesRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{98} + return file_lightning_proto_rawDescGZIP(), []int{99} } func (x *QueryRoutesRequest) GetPubKey() string { @@ -9852,7 +9985,7 @@ type NodePair struct { func (x *NodePair) Reset() { *x = NodePair{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[99] + mi := &file_lightning_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9865,7 +9998,7 @@ func (x *NodePair) String() string { func (*NodePair) ProtoMessage() {} func (x *NodePair) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[99] + mi := &file_lightning_proto_msgTypes[100] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9878,7 +10011,7 @@ func (x *NodePair) ProtoReflect() protoreflect.Message { // Deprecated: Use NodePair.ProtoReflect.Descriptor instead. func (*NodePair) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{99} + return file_lightning_proto_rawDescGZIP(), []int{100} } func (x *NodePair) GetFrom() []byte { @@ -9912,7 +10045,7 @@ type EdgeLocator struct { func (x *EdgeLocator) Reset() { *x = EdgeLocator{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[100] + mi := &file_lightning_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9925,7 +10058,7 @@ func (x *EdgeLocator) String() string { func (*EdgeLocator) ProtoMessage() {} func (x *EdgeLocator) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[100] + mi := &file_lightning_proto_msgTypes[101] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9938,7 +10071,7 @@ func (x *EdgeLocator) ProtoReflect() protoreflect.Message { // Deprecated: Use EdgeLocator.ProtoReflect.Descriptor instead. func (*EdgeLocator) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{100} + return file_lightning_proto_rawDescGZIP(), []int{101} } func (x *EdgeLocator) GetChannelId() uint64 { @@ -9971,7 +10104,7 @@ type QueryRoutesResponse struct { func (x *QueryRoutesResponse) Reset() { *x = QueryRoutesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[101] + mi := &file_lightning_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9984,7 +10117,7 @@ func (x *QueryRoutesResponse) String() string { func (*QueryRoutesResponse) ProtoMessage() {} func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[101] + mi := &file_lightning_proto_msgTypes[102] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9997,7 +10130,7 @@ func (x *QueryRoutesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRoutesResponse.ProtoReflect.Descriptor instead. func (*QueryRoutesResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{101} + return file_lightning_proto_rawDescGZIP(), []int{102} } func (x *QueryRoutesResponse) GetRoutes() []*Route { @@ -10081,7 +10214,7 @@ type Hop struct { func (x *Hop) Reset() { *x = Hop{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[102] + mi := &file_lightning_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10094,7 +10227,7 @@ func (x *Hop) String() string { func (*Hop) ProtoMessage() {} func (x *Hop) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[102] + mi := &file_lightning_proto_msgTypes[103] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10107,7 +10240,7 @@ func (x *Hop) ProtoReflect() protoreflect.Message { // Deprecated: Use Hop.ProtoReflect.Descriptor instead. func (*Hop) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{102} + return file_lightning_proto_rawDescGZIP(), []int{103} } func (x *Hop) GetChanId() uint64 { @@ -10247,7 +10380,7 @@ type MPPRecord struct { func (x *MPPRecord) Reset() { *x = MPPRecord{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[103] + mi := &file_lightning_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10260,7 +10393,7 @@ func (x *MPPRecord) String() string { func (*MPPRecord) ProtoMessage() {} func (x *MPPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[103] + mi := &file_lightning_proto_msgTypes[104] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10273,7 +10406,7 @@ func (x *MPPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use MPPRecord.ProtoReflect.Descriptor instead. func (*MPPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{103} + return file_lightning_proto_rawDescGZIP(), []int{104} } func (x *MPPRecord) GetPaymentAddr() []byte { @@ -10303,7 +10436,7 @@ type AMPRecord struct { func (x *AMPRecord) Reset() { *x = AMPRecord{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[104] + mi := &file_lightning_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10316,7 +10449,7 @@ func (x *AMPRecord) String() string { func (*AMPRecord) ProtoMessage() {} func (x *AMPRecord) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[104] + mi := &file_lightning_proto_msgTypes[105] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10329,7 +10462,7 @@ func (x *AMPRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPRecord.ProtoReflect.Descriptor instead. func (*AMPRecord) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{104} + return file_lightning_proto_rawDescGZIP(), []int{105} } func (x *AMPRecord) GetRootShare() []byte { @@ -10388,12 +10521,20 @@ type Route struct { TotalFeesMsat int64 `protobuf:"varint,5,opt,name=total_fees_msat,json=totalFeesMsat,proto3" json:"total_fees_msat,omitempty"` // The total amount in millisatoshis. TotalAmtMsat int64 `protobuf:"varint,6,opt,name=total_amt_msat,json=totalAmtMsat,proto3" json:"total_amt_msat,omitempty"` + // The actual on-chain amount that was sent out to the first hop. This value is + // only different from the total_amt_msat field if this is a custom channel + // payment and the value transported in the HTLC is different from the BTC + // amount in the HTLC. If this value is zero, then this is an old payment that + // didn't have this value yet and can be ignored. + FirstHopAmountMsat int64 `protobuf:"varint,7,opt,name=first_hop_amount_msat,json=firstHopAmountMsat,proto3" json:"first_hop_amount_msat,omitempty"` + // Custom channel data that might be populated in custom channels. + CustomChannelData []byte `protobuf:"bytes,8,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` } func (x *Route) Reset() { *x = Route{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[105] + mi := &file_lightning_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10406,7 +10547,7 @@ func (x *Route) String() string { func (*Route) ProtoMessage() {} func (x *Route) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[105] + mi := &file_lightning_proto_msgTypes[106] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10419,7 +10560,7 @@ func (x *Route) ProtoReflect() protoreflect.Message { // Deprecated: Use Route.ProtoReflect.Descriptor instead. func (*Route) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{105} + return file_lightning_proto_rawDescGZIP(), []int{106} } func (x *Route) GetTotalTimeLock() uint32 { @@ -10466,6 +10607,20 @@ func (x *Route) GetTotalAmtMsat() int64 { return 0 } +func (x *Route) GetFirstHopAmountMsat() int64 { + if x != nil { + return x.FirstHopAmountMsat + } + return 0 +} + +func (x *Route) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + type NodeInfoRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -10480,7 +10635,7 @@ type NodeInfoRequest struct { func (x *NodeInfoRequest) Reset() { *x = NodeInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[106] + mi := &file_lightning_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10493,7 +10648,7 @@ func (x *NodeInfoRequest) String() string { func (*NodeInfoRequest) ProtoMessage() {} func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[106] + mi := &file_lightning_proto_msgTypes[107] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10506,7 +10661,7 @@ func (x *NodeInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfoRequest.ProtoReflect.Descriptor instead. func (*NodeInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{106} + return file_lightning_proto_rawDescGZIP(), []int{107} } func (x *NodeInfoRequest) GetPubKey() string { @@ -10544,7 +10699,7 @@ type NodeInfo struct { func (x *NodeInfo) Reset() { *x = NodeInfo{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[107] + mi := &file_lightning_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10557,7 +10712,7 @@ func (x *NodeInfo) String() string { func (*NodeInfo) ProtoMessage() {} func (x *NodeInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[107] + mi := &file_lightning_proto_msgTypes[108] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10570,7 +10725,7 @@ func (x *NodeInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. func (*NodeInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{107} + return file_lightning_proto_rawDescGZIP(), []int{108} } func (x *NodeInfo) GetNode() *LightningNode { @@ -10623,7 +10778,7 @@ type LightningNode struct { func (x *LightningNode) Reset() { *x = LightningNode{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[108] + mi := &file_lightning_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10636,7 +10791,7 @@ func (x *LightningNode) String() string { func (*LightningNode) ProtoMessage() {} func (x *LightningNode) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[108] + mi := &file_lightning_proto_msgTypes[109] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10649,7 +10804,7 @@ func (x *LightningNode) ProtoReflect() protoreflect.Message { // Deprecated: Use LightningNode.ProtoReflect.Descriptor instead. func (*LightningNode) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{108} + return file_lightning_proto_rawDescGZIP(), []int{109} } func (x *LightningNode) GetLastUpdate() uint32 { @@ -10713,7 +10868,7 @@ type NodeAddress struct { func (x *NodeAddress) Reset() { *x = NodeAddress{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[109] + mi := &file_lightning_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10726,7 +10881,7 @@ func (x *NodeAddress) String() string { func (*NodeAddress) ProtoMessage() {} func (x *NodeAddress) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[109] + mi := &file_lightning_proto_msgTypes[110] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10739,7 +10894,7 @@ func (x *NodeAddress) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeAddress.ProtoReflect.Descriptor instead. func (*NodeAddress) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{109} + return file_lightning_proto_rawDescGZIP(), []int{110} } func (x *NodeAddress) GetNetwork() string { @@ -10777,7 +10932,7 @@ type RoutingPolicy struct { func (x *RoutingPolicy) Reset() { *x = RoutingPolicy{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[110] + mi := &file_lightning_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10790,7 +10945,7 @@ func (x *RoutingPolicy) String() string { func (*RoutingPolicy) ProtoMessage() {} func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[110] + mi := &file_lightning_proto_msgTypes[111] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10803,7 +10958,7 @@ func (x *RoutingPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use RoutingPolicy.ProtoReflect.Descriptor instead. func (*RoutingPolicy) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{110} + return file_lightning_proto_rawDescGZIP(), []int{111} } func (x *RoutingPolicy) GetTimeLockDelta() uint32 { @@ -10905,7 +11060,7 @@ type ChannelEdge struct { func (x *ChannelEdge) Reset() { *x = ChannelEdge{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[111] + mi := &file_lightning_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10918,7 +11073,7 @@ func (x *ChannelEdge) String() string { func (*ChannelEdge) ProtoMessage() {} func (x *ChannelEdge) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[111] + mi := &file_lightning_proto_msgTypes[112] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10931,7 +11086,7 @@ func (x *ChannelEdge) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdge.ProtoReflect.Descriptor instead. func (*ChannelEdge) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{111} + return file_lightning_proto_rawDescGZIP(), []int{112} } func (x *ChannelEdge) GetChannelId() uint64 { @@ -11012,7 +11167,7 @@ type ChannelGraphRequest struct { func (x *ChannelGraphRequest) Reset() { *x = ChannelGraphRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[112] + mi := &file_lightning_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11025,7 +11180,7 @@ func (x *ChannelGraphRequest) String() string { func (*ChannelGraphRequest) ProtoMessage() {} func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[112] + mi := &file_lightning_proto_msgTypes[113] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11038,7 +11193,7 @@ func (x *ChannelGraphRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraphRequest.ProtoReflect.Descriptor instead. func (*ChannelGraphRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{112} + return file_lightning_proto_rawDescGZIP(), []int{113} } func (x *ChannelGraphRequest) GetIncludeUnannounced() bool { @@ -11063,7 +11218,7 @@ type ChannelGraph struct { func (x *ChannelGraph) Reset() { *x = ChannelGraph{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[113] + mi := &file_lightning_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11076,7 +11231,7 @@ func (x *ChannelGraph) String() string { func (*ChannelGraph) ProtoMessage() {} func (x *ChannelGraph) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[113] + mi := &file_lightning_proto_msgTypes[114] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11089,7 +11244,7 @@ func (x *ChannelGraph) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelGraph.ProtoReflect.Descriptor instead. func (*ChannelGraph) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{113} + return file_lightning_proto_rawDescGZIP(), []int{114} } func (x *ChannelGraph) GetNodes() []*LightningNode { @@ -11118,7 +11273,7 @@ type NodeMetricsRequest struct { func (x *NodeMetricsRequest) Reset() { *x = NodeMetricsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[114] + mi := &file_lightning_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11131,7 +11286,7 @@ func (x *NodeMetricsRequest) String() string { func (*NodeMetricsRequest) ProtoMessage() {} func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[114] + mi := &file_lightning_proto_msgTypes[115] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11144,7 +11299,7 @@ func (x *NodeMetricsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsRequest.ProtoReflect.Descriptor instead. func (*NodeMetricsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{114} + return file_lightning_proto_rawDescGZIP(), []int{115} } func (x *NodeMetricsRequest) GetTypes() []NodeMetricType { @@ -11170,7 +11325,7 @@ type NodeMetricsResponse struct { func (x *NodeMetricsResponse) Reset() { *x = NodeMetricsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[115] + mi := &file_lightning_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11183,7 +11338,7 @@ func (x *NodeMetricsResponse) String() string { func (*NodeMetricsResponse) ProtoMessage() {} func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[115] + mi := &file_lightning_proto_msgTypes[116] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11196,7 +11351,7 @@ func (x *NodeMetricsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeMetricsResponse.ProtoReflect.Descriptor instead. func (*NodeMetricsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{115} + return file_lightning_proto_rawDescGZIP(), []int{116} } func (x *NodeMetricsResponse) GetBetweennessCentrality() map[string]*FloatMetric { @@ -11220,7 +11375,7 @@ type FloatMetric struct { func (x *FloatMetric) Reset() { *x = FloatMetric{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[116] + mi := &file_lightning_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11233,7 +11388,7 @@ func (x *FloatMetric) String() string { func (*FloatMetric) ProtoMessage() {} func (x *FloatMetric) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[116] + mi := &file_lightning_proto_msgTypes[117] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11246,7 +11401,7 @@ func (x *FloatMetric) ProtoReflect() protoreflect.Message { // Deprecated: Use FloatMetric.ProtoReflect.Descriptor instead. func (*FloatMetric) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{116} + return file_lightning_proto_rawDescGZIP(), []int{117} } func (x *FloatMetric) GetValue() float64 { @@ -11280,7 +11435,7 @@ type ChanInfoRequest struct { func (x *ChanInfoRequest) Reset() { *x = ChanInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[117] + mi := &file_lightning_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11293,7 +11448,7 @@ func (x *ChanInfoRequest) String() string { func (*ChanInfoRequest) ProtoMessage() {} func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[117] + mi := &file_lightning_proto_msgTypes[118] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11306,7 +11461,7 @@ func (x *ChanInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanInfoRequest.ProtoReflect.Descriptor instead. func (*ChanInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{117} + return file_lightning_proto_rawDescGZIP(), []int{118} } func (x *ChanInfoRequest) GetChanId() uint64 { @@ -11332,7 +11487,7 @@ type NetworkInfoRequest struct { func (x *NetworkInfoRequest) Reset() { *x = NetworkInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[118] + mi := &file_lightning_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11345,7 +11500,7 @@ func (x *NetworkInfoRequest) String() string { func (*NetworkInfoRequest) ProtoMessage() {} func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[118] + mi := &file_lightning_proto_msgTypes[119] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11358,7 +11513,7 @@ func (x *NetworkInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfoRequest.ProtoReflect.Descriptor instead. func (*NetworkInfoRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{118} + return file_lightning_proto_rawDescGZIP(), []int{119} } type NetworkInfo struct { @@ -11383,7 +11538,7 @@ type NetworkInfo struct { func (x *NetworkInfo) Reset() { *x = NetworkInfo{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[119] + mi := &file_lightning_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11396,7 +11551,7 @@ func (x *NetworkInfo) String() string { func (*NetworkInfo) ProtoMessage() {} func (x *NetworkInfo) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[119] + mi := &file_lightning_proto_msgTypes[120] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11409,7 +11564,7 @@ func (x *NetworkInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NetworkInfo.ProtoReflect.Descriptor instead. func (*NetworkInfo) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{119} + return file_lightning_proto_rawDescGZIP(), []int{120} } func (x *NetworkInfo) GetGraphDiameter() uint32 { @@ -11498,7 +11653,7 @@ type StopRequest struct { func (x *StopRequest) Reset() { *x = StopRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[120] + mi := &file_lightning_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11511,7 +11666,7 @@ func (x *StopRequest) String() string { func (*StopRequest) ProtoMessage() {} func (x *StopRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[120] + mi := &file_lightning_proto_msgTypes[121] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11524,7 +11679,7 @@ func (x *StopRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopRequest.ProtoReflect.Descriptor instead. func (*StopRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{120} + return file_lightning_proto_rawDescGZIP(), []int{121} } type StopResponse struct { @@ -11536,7 +11691,7 @@ type StopResponse struct { func (x *StopResponse) Reset() { *x = StopResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[121] + mi := &file_lightning_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11549,7 +11704,7 @@ func (x *StopResponse) String() string { func (*StopResponse) ProtoMessage() {} func (x *StopResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[121] + mi := &file_lightning_proto_msgTypes[122] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11562,7 +11717,7 @@ func (x *StopResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopResponse.ProtoReflect.Descriptor instead. func (*StopResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{121} + return file_lightning_proto_rawDescGZIP(), []int{122} } type GraphTopologySubscription struct { @@ -11574,7 +11729,7 @@ type GraphTopologySubscription struct { func (x *GraphTopologySubscription) Reset() { *x = GraphTopologySubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[122] + mi := &file_lightning_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11587,7 +11742,7 @@ func (x *GraphTopologySubscription) String() string { func (*GraphTopologySubscription) ProtoMessage() {} func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[122] + mi := &file_lightning_proto_msgTypes[123] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11600,7 +11755,7 @@ func (x *GraphTopologySubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologySubscription.ProtoReflect.Descriptor instead. func (*GraphTopologySubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{122} + return file_lightning_proto_rawDescGZIP(), []int{123} } type GraphTopologyUpdate struct { @@ -11616,7 +11771,7 @@ type GraphTopologyUpdate struct { func (x *GraphTopologyUpdate) Reset() { *x = GraphTopologyUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[123] + mi := &file_lightning_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11629,7 +11784,7 @@ func (x *GraphTopologyUpdate) String() string { func (*GraphTopologyUpdate) ProtoMessage() {} func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[123] + mi := &file_lightning_proto_msgTypes[124] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11642,7 +11797,7 @@ func (x *GraphTopologyUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphTopologyUpdate.ProtoReflect.Descriptor instead. func (*GraphTopologyUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{123} + return file_lightning_proto_rawDescGZIP(), []int{124} } func (x *GraphTopologyUpdate) GetNodeUpdates() []*NodeUpdate { @@ -11691,7 +11846,7 @@ type NodeUpdate struct { func (x *NodeUpdate) Reset() { *x = NodeUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[124] + mi := &file_lightning_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11704,7 +11859,7 @@ func (x *NodeUpdate) String() string { func (*NodeUpdate) ProtoMessage() {} func (x *NodeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[124] + mi := &file_lightning_proto_msgTypes[125] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11717,7 +11872,7 @@ func (x *NodeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeUpdate.ProtoReflect.Descriptor instead. func (*NodeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{124} + return file_lightning_proto_rawDescGZIP(), []int{125} } // Deprecated: Marked as deprecated in lightning.proto. @@ -11790,7 +11945,7 @@ type ChannelEdgeUpdate struct { func (x *ChannelEdgeUpdate) Reset() { *x = ChannelEdgeUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[125] + mi := &file_lightning_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11803,7 +11958,7 @@ func (x *ChannelEdgeUpdate) String() string { func (*ChannelEdgeUpdate) ProtoMessage() {} func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[125] + mi := &file_lightning_proto_msgTypes[126] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11816,7 +11971,7 @@ func (x *ChannelEdgeUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelEdgeUpdate.ProtoReflect.Descriptor instead. func (*ChannelEdgeUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{125} + return file_lightning_proto_rawDescGZIP(), []int{126} } func (x *ChannelEdgeUpdate) GetChanId() uint64 { @@ -11878,7 +12033,7 @@ type ClosedChannelUpdate struct { func (x *ClosedChannelUpdate) Reset() { *x = ClosedChannelUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[126] + mi := &file_lightning_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11891,7 +12046,7 @@ func (x *ClosedChannelUpdate) String() string { func (*ClosedChannelUpdate) ProtoMessage() {} func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[126] + mi := &file_lightning_proto_msgTypes[127] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11904,7 +12059,7 @@ func (x *ClosedChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChannelUpdate.ProtoReflect.Descriptor instead. func (*ClosedChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{126} + return file_lightning_proto_rawDescGZIP(), []int{127} } func (x *ClosedChannelUpdate) GetChanId() uint64 { @@ -11956,7 +12111,7 @@ type HopHint struct { func (x *HopHint) Reset() { *x = HopHint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[127] + mi := &file_lightning_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11969,7 +12124,7 @@ func (x *HopHint) String() string { func (*HopHint) ProtoMessage() {} func (x *HopHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[127] + mi := &file_lightning_proto_msgTypes[128] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11982,7 +12137,7 @@ func (x *HopHint) ProtoReflect() protoreflect.Message { // Deprecated: Use HopHint.ProtoReflect.Descriptor instead. func (*HopHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{127} + return file_lightning_proto_rawDescGZIP(), []int{128} } func (x *HopHint) GetNodeId() string { @@ -12031,7 +12186,7 @@ type SetID struct { func (x *SetID) Reset() { *x = SetID{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[128] + mi := &file_lightning_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12044,7 +12199,7 @@ func (x *SetID) String() string { func (*SetID) ProtoMessage() {} func (x *SetID) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[128] + mi := &file_lightning_proto_msgTypes[129] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12057,7 +12212,7 @@ func (x *SetID) ProtoReflect() protoreflect.Message { // Deprecated: Use SetID.ProtoReflect.Descriptor instead. func (*SetID) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{128} + return file_lightning_proto_rawDescGZIP(), []int{129} } func (x *SetID) GetSetId() []byte { @@ -12080,7 +12235,7 @@ type RouteHint struct { func (x *RouteHint) Reset() { *x = RouteHint{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[129] + mi := &file_lightning_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12093,7 +12248,7 @@ func (x *RouteHint) String() string { func (*RouteHint) ProtoMessage() {} func (x *RouteHint) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[129] + mi := &file_lightning_proto_msgTypes[130] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12106,7 +12261,7 @@ func (x *RouteHint) ProtoReflect() protoreflect.Message { // Deprecated: Use RouteHint.ProtoReflect.Descriptor instead. func (*RouteHint) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{129} + return file_lightning_proto_rawDescGZIP(), []int{130} } func (x *RouteHint) GetHopHints() []*HopHint { @@ -12144,7 +12299,7 @@ type BlindedPaymentPath struct { func (x *BlindedPaymentPath) Reset() { *x = BlindedPaymentPath{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[130] + mi := &file_lightning_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12157,7 +12312,7 @@ func (x *BlindedPaymentPath) String() string { func (*BlindedPaymentPath) ProtoMessage() {} func (x *BlindedPaymentPath) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[130] + mi := &file_lightning_proto_msgTypes[131] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12170,7 +12325,7 @@ func (x *BlindedPaymentPath) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPaymentPath.ProtoReflect.Descriptor instead. func (*BlindedPaymentPath) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{130} + return file_lightning_proto_rawDescGZIP(), []int{131} } func (x *BlindedPaymentPath) GetBlindedPath() *BlindedPath { @@ -12240,7 +12395,7 @@ type BlindedPath struct { func (x *BlindedPath) Reset() { *x = BlindedPath{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[131] + mi := &file_lightning_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12253,7 +12408,7 @@ func (x *BlindedPath) String() string { func (*BlindedPath) ProtoMessage() {} func (x *BlindedPath) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[131] + mi := &file_lightning_proto_msgTypes[132] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12266,7 +12421,7 @@ func (x *BlindedPath) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPath.ProtoReflect.Descriptor instead. func (*BlindedPath) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{131} + return file_lightning_proto_rawDescGZIP(), []int{132} } func (x *BlindedPath) GetIntroductionNode() []byte { @@ -12304,7 +12459,7 @@ type BlindedHop struct { func (x *BlindedHop) Reset() { *x = BlindedHop{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[132] + mi := &file_lightning_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12317,7 +12472,7 @@ func (x *BlindedHop) String() string { func (*BlindedHop) ProtoMessage() {} func (x *BlindedHop) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[132] + mi := &file_lightning_proto_msgTypes[133] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12330,7 +12485,7 @@ func (x *BlindedHop) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedHop.ProtoReflect.Descriptor instead. func (*BlindedHop) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{132} + return file_lightning_proto_rawDescGZIP(), []int{133} } func (x *BlindedHop) GetBlindedNode() []byte { @@ -12365,7 +12520,7 @@ type AMPInvoiceState struct { func (x *AMPInvoiceState) Reset() { *x = AMPInvoiceState{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[133] + mi := &file_lightning_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12378,7 +12533,7 @@ func (x *AMPInvoiceState) String() string { func (*AMPInvoiceState) ProtoMessage() {} func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[133] + mi := &file_lightning_proto_msgTypes[134] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12391,7 +12546,7 @@ func (x *AMPInvoiceState) ProtoReflect() protoreflect.Message { // Deprecated: Use AMPInvoiceState.ProtoReflect.Descriptor instead. func (*AMPInvoiceState) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{133} + return file_lightning_proto_rawDescGZIP(), []int{134} } func (x *AMPInvoiceState) GetState() InvoiceHTLCState { @@ -12558,7 +12713,7 @@ type Invoice struct { func (x *Invoice) Reset() { *x = Invoice{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[134] + mi := &file_lightning_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12571,7 +12726,7 @@ func (x *Invoice) String() string { func (*Invoice) ProtoMessage() {} func (x *Invoice) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[134] + mi := &file_lightning_proto_msgTypes[135] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12584,7 +12739,7 @@ func (x *Invoice) ProtoReflect() protoreflect.Message { // Deprecated: Use Invoice.ProtoReflect.Descriptor instead. func (*Invoice) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{134} + return file_lightning_proto_rawDescGZIP(), []int{135} } func (x *Invoice) GetMemo() string { @@ -12817,7 +12972,7 @@ type BlindedPathConfig struct { func (x *BlindedPathConfig) Reset() { *x = BlindedPathConfig{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[135] + mi := &file_lightning_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12830,7 +12985,7 @@ func (x *BlindedPathConfig) String() string { func (*BlindedPathConfig) ProtoMessage() {} func (x *BlindedPathConfig) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[135] + mi := &file_lightning_proto_msgTypes[136] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12843,7 +12998,7 @@ func (x *BlindedPathConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use BlindedPathConfig.ProtoReflect.Descriptor instead. func (*BlindedPathConfig) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{135} + return file_lightning_proto_rawDescGZIP(), []int{136} } func (x *BlindedPathConfig) GetMinNumRealHops() uint32 { @@ -12902,12 +13057,14 @@ type InvoiceHTLC struct { MppTotalAmtMsat uint64 `protobuf:"varint,10,opt,name=mpp_total_amt_msat,json=mppTotalAmtMsat,proto3" json:"mpp_total_amt_msat,omitempty"` // Details relevant to AMP HTLCs, only populated if this is an AMP HTLC. Amp *AMP `protobuf:"bytes,11,opt,name=amp,proto3" json:"amp,omitempty"` + // Custom channel data that might be populated in custom channels. + CustomChannelData []byte `protobuf:"bytes,12,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` } func (x *InvoiceHTLC) Reset() { *x = InvoiceHTLC{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[136] + mi := &file_lightning_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12920,7 +13077,7 @@ func (x *InvoiceHTLC) String() string { func (*InvoiceHTLC) ProtoMessage() {} func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[136] + mi := &file_lightning_proto_msgTypes[137] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12933,7 +13090,7 @@ func (x *InvoiceHTLC) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceHTLC.ProtoReflect.Descriptor instead. func (*InvoiceHTLC) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{136} + return file_lightning_proto_rawDescGZIP(), []int{137} } func (x *InvoiceHTLC) GetChanId() uint64 { @@ -13013,6 +13170,13 @@ func (x *InvoiceHTLC) GetAmp() *AMP { return nil } +func (x *InvoiceHTLC) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + // Details specific to AMP HTLCs. type AMP struct { state protoimpl.MessageState @@ -13038,7 +13202,7 @@ type AMP struct { func (x *AMP) Reset() { *x = AMP{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[137] + mi := &file_lightning_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13051,7 +13215,7 @@ func (x *AMP) String() string { func (*AMP) ProtoMessage() {} func (x *AMP) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[137] + mi := &file_lightning_proto_msgTypes[138] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13064,7 +13228,7 @@ func (x *AMP) ProtoReflect() protoreflect.Message { // Deprecated: Use AMP.ProtoReflect.Descriptor instead. func (*AMP) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{137} + return file_lightning_proto_rawDescGZIP(), []int{138} } func (x *AMP) GetRootShare() []byte { @@ -13126,7 +13290,7 @@ type AddInvoiceResponse struct { func (x *AddInvoiceResponse) Reset() { *x = AddInvoiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[138] + mi := &file_lightning_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13139,7 +13303,7 @@ func (x *AddInvoiceResponse) String() string { func (*AddInvoiceResponse) ProtoMessage() {} func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[138] + mi := &file_lightning_proto_msgTypes[139] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13152,7 +13316,7 @@ func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead. func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{138} + return file_lightning_proto_rawDescGZIP(), []int{139} } func (x *AddInvoiceResponse) GetRHash() []byte { @@ -13203,7 +13367,7 @@ type PaymentHash struct { func (x *PaymentHash) Reset() { *x = PaymentHash{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[139] + mi := &file_lightning_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13216,7 +13380,7 @@ func (x *PaymentHash) String() string { func (*PaymentHash) ProtoMessage() {} func (x *PaymentHash) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[139] + mi := &file_lightning_proto_msgTypes[140] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13229,7 +13393,7 @@ func (x *PaymentHash) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentHash.ProtoReflect.Descriptor instead. func (*PaymentHash) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{139} + return file_lightning_proto_rawDescGZIP(), []int{140} } // Deprecated: Marked as deprecated in lightning.proto. @@ -13274,7 +13438,7 @@ type ListInvoiceRequest struct { func (x *ListInvoiceRequest) Reset() { *x = ListInvoiceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[140] + mi := &file_lightning_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13287,7 +13451,7 @@ func (x *ListInvoiceRequest) String() string { func (*ListInvoiceRequest) ProtoMessage() {} func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[140] + mi := &file_lightning_proto_msgTypes[141] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13300,7 +13464,7 @@ func (x *ListInvoiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceRequest.ProtoReflect.Descriptor instead. func (*ListInvoiceRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{140} + return file_lightning_proto_rawDescGZIP(), []int{141} } func (x *ListInvoiceRequest) GetPendingOnly() bool { @@ -13364,7 +13528,7 @@ type ListInvoiceResponse struct { func (x *ListInvoiceResponse) Reset() { *x = ListInvoiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[141] + mi := &file_lightning_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13377,7 +13541,7 @@ func (x *ListInvoiceResponse) String() string { func (*ListInvoiceResponse) ProtoMessage() {} func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[141] + mi := &file_lightning_proto_msgTypes[142] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13390,7 +13554,7 @@ func (x *ListInvoiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInvoiceResponse.ProtoReflect.Descriptor instead. func (*ListInvoiceResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{141} + return file_lightning_proto_rawDescGZIP(), []int{142} } func (x *ListInvoiceResponse) GetInvoices() []*Invoice { @@ -13434,7 +13598,7 @@ type InvoiceSubscription struct { func (x *InvoiceSubscription) Reset() { *x = InvoiceSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[142] + mi := &file_lightning_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13447,7 +13611,7 @@ func (x *InvoiceSubscription) String() string { func (*InvoiceSubscription) ProtoMessage() {} func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[142] + mi := &file_lightning_proto_msgTypes[143] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13460,7 +13624,7 @@ func (x *InvoiceSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use InvoiceSubscription.ProtoReflect.Descriptor instead. func (*InvoiceSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{142} + return file_lightning_proto_rawDescGZIP(), []int{143} } func (x *InvoiceSubscription) GetAddIndex() uint64 { @@ -13519,12 +13683,15 @@ type Payment struct { // older versions of lnd. PaymentIndex uint64 `protobuf:"varint,15,opt,name=payment_index,json=paymentIndex,proto3" json:"payment_index,omitempty"` FailureReason PaymentFailureReason `protobuf:"varint,16,opt,name=failure_reason,json=failureReason,proto3,enum=lnrpc.PaymentFailureReason" json:"failure_reason,omitempty"` + // The custom TLV records that were sent to the first hop as part of the HTLC + // wire message for this payment. + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,17,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *Payment) Reset() { *x = Payment{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[143] + mi := &file_lightning_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13537,7 +13704,7 @@ func (x *Payment) String() string { func (*Payment) ProtoMessage() {} func (x *Payment) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[143] + mi := &file_lightning_proto_msgTypes[144] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13550,7 +13717,7 @@ func (x *Payment) ProtoReflect() protoreflect.Message { // Deprecated: Use Payment.ProtoReflect.Descriptor instead. func (*Payment) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{143} + return file_lightning_proto_rawDescGZIP(), []int{144} } func (x *Payment) GetPaymentHash() string { @@ -13661,6 +13828,13 @@ func (x *Payment) GetFailureReason() PaymentFailureReason { return PaymentFailureReason_FAILURE_REASON_NONE } +func (x *Payment) GetFirstHopCustomRecords() map[uint64][]byte { + if x != nil { + return x.FirstHopCustomRecords + } + return nil +} + type HTLCAttempt struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -13686,7 +13860,7 @@ type HTLCAttempt struct { func (x *HTLCAttempt) Reset() { *x = HTLCAttempt{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[144] + mi := &file_lightning_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13699,7 +13873,7 @@ func (x *HTLCAttempt) String() string { func (*HTLCAttempt) ProtoMessage() {} func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[144] + mi := &file_lightning_proto_msgTypes[145] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13712,7 +13886,7 @@ func (x *HTLCAttempt) ProtoReflect() protoreflect.Message { // Deprecated: Use HTLCAttempt.ProtoReflect.Descriptor instead. func (*HTLCAttempt) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{144} + return file_lightning_proto_rawDescGZIP(), []int{145} } func (x *HTLCAttempt) GetAttemptId() uint64 { @@ -13802,7 +13976,7 @@ type ListPaymentsRequest struct { func (x *ListPaymentsRequest) Reset() { *x = ListPaymentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[145] + mi := &file_lightning_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13815,7 +13989,7 @@ func (x *ListPaymentsRequest) String() string { func (*ListPaymentsRequest) ProtoMessage() {} func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[145] + mi := &file_lightning_proto_msgTypes[146] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13828,7 +14002,7 @@ func (x *ListPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsRequest.ProtoReflect.Descriptor instead. func (*ListPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{145} + return file_lightning_proto_rawDescGZIP(), []int{146} } func (x *ListPaymentsRequest) GetIncludeIncomplete() bool { @@ -13903,7 +14077,7 @@ type ListPaymentsResponse struct { func (x *ListPaymentsResponse) Reset() { *x = ListPaymentsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[146] + mi := &file_lightning_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13916,7 +14090,7 @@ func (x *ListPaymentsResponse) String() string { func (*ListPaymentsResponse) ProtoMessage() {} func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[146] + mi := &file_lightning_proto_msgTypes[147] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -13929,7 +14103,7 @@ func (x *ListPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPaymentsResponse.ProtoReflect.Descriptor instead. func (*ListPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{146} + return file_lightning_proto_rawDescGZIP(), []int{147} } func (x *ListPaymentsResponse) GetPayments() []*Payment { @@ -13974,7 +14148,7 @@ type DeletePaymentRequest struct { func (x *DeletePaymentRequest) Reset() { *x = DeletePaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[147] + mi := &file_lightning_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -13987,7 +14161,7 @@ func (x *DeletePaymentRequest) String() string { func (*DeletePaymentRequest) ProtoMessage() {} func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[147] + mi := &file_lightning_proto_msgTypes[148] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14000,7 +14174,7 @@ func (x *DeletePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentRequest.ProtoReflect.Descriptor instead. func (*DeletePaymentRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{147} + return file_lightning_proto_rawDescGZIP(), []int{148} } func (x *DeletePaymentRequest) GetPaymentHash() []byte { @@ -14034,7 +14208,7 @@ type DeleteAllPaymentsRequest struct { func (x *DeleteAllPaymentsRequest) Reset() { *x = DeleteAllPaymentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[148] + mi := &file_lightning_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14047,7 +14221,7 @@ func (x *DeleteAllPaymentsRequest) String() string { func (*DeleteAllPaymentsRequest) ProtoMessage() {} func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[148] + mi := &file_lightning_proto_msgTypes[149] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14060,7 +14234,7 @@ func (x *DeleteAllPaymentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsRequest.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{148} + return file_lightning_proto_rawDescGZIP(), []int{149} } func (x *DeleteAllPaymentsRequest) GetFailedPaymentsOnly() bool { @@ -14093,7 +14267,7 @@ type DeletePaymentResponse struct { func (x *DeletePaymentResponse) Reset() { *x = DeletePaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[149] + mi := &file_lightning_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14106,7 +14280,7 @@ func (x *DeletePaymentResponse) String() string { func (*DeletePaymentResponse) ProtoMessage() {} func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[149] + mi := &file_lightning_proto_msgTypes[150] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14119,7 +14293,7 @@ func (x *DeletePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePaymentResponse.ProtoReflect.Descriptor instead. func (*DeletePaymentResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{149} + return file_lightning_proto_rawDescGZIP(), []int{150} } type DeleteAllPaymentsResponse struct { @@ -14131,7 +14305,7 @@ type DeleteAllPaymentsResponse struct { func (x *DeleteAllPaymentsResponse) Reset() { *x = DeleteAllPaymentsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[150] + mi := &file_lightning_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14144,7 +14318,7 @@ func (x *DeleteAllPaymentsResponse) String() string { func (*DeleteAllPaymentsResponse) ProtoMessage() {} func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[150] + mi := &file_lightning_proto_msgTypes[151] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14157,7 +14331,7 @@ func (x *DeleteAllPaymentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAllPaymentsResponse.ProtoReflect.Descriptor instead. func (*DeleteAllPaymentsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{150} + return file_lightning_proto_rawDescGZIP(), []int{151} } type AbandonChannelRequest struct { @@ -14176,7 +14350,7 @@ type AbandonChannelRequest struct { func (x *AbandonChannelRequest) Reset() { *x = AbandonChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[151] + mi := &file_lightning_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14189,7 +14363,7 @@ func (x *AbandonChannelRequest) String() string { func (*AbandonChannelRequest) ProtoMessage() {} func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[151] + mi := &file_lightning_proto_msgTypes[152] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14202,7 +14376,7 @@ func (x *AbandonChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelRequest.ProtoReflect.Descriptor instead. func (*AbandonChannelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{151} + return file_lightning_proto_rawDescGZIP(), []int{152} } func (x *AbandonChannelRequest) GetChannelPoint() *ChannelPoint { @@ -14235,7 +14409,7 @@ type AbandonChannelResponse struct { func (x *AbandonChannelResponse) Reset() { *x = AbandonChannelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[152] + mi := &file_lightning_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14248,7 +14422,7 @@ func (x *AbandonChannelResponse) String() string { func (*AbandonChannelResponse) ProtoMessage() {} func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[152] + mi := &file_lightning_proto_msgTypes[153] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14261,7 +14435,7 @@ func (x *AbandonChannelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonChannelResponse.ProtoReflect.Descriptor instead. func (*AbandonChannelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{152} + return file_lightning_proto_rawDescGZIP(), []int{153} } type DebugLevelRequest struct { @@ -14276,7 +14450,7 @@ type DebugLevelRequest struct { func (x *DebugLevelRequest) Reset() { *x = DebugLevelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[153] + mi := &file_lightning_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14289,7 +14463,7 @@ func (x *DebugLevelRequest) String() string { func (*DebugLevelRequest) ProtoMessage() {} func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[153] + mi := &file_lightning_proto_msgTypes[154] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14302,7 +14476,7 @@ func (x *DebugLevelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelRequest.ProtoReflect.Descriptor instead. func (*DebugLevelRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{153} + return file_lightning_proto_rawDescGZIP(), []int{154} } func (x *DebugLevelRequest) GetShow() bool { @@ -14330,7 +14504,7 @@ type DebugLevelResponse struct { func (x *DebugLevelResponse) Reset() { *x = DebugLevelResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[154] + mi := &file_lightning_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14343,7 +14517,7 @@ func (x *DebugLevelResponse) String() string { func (*DebugLevelResponse) ProtoMessage() {} func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[154] + mi := &file_lightning_proto_msgTypes[155] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14356,7 +14530,7 @@ func (x *DebugLevelResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugLevelResponse.ProtoReflect.Descriptor instead. func (*DebugLevelResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{154} + return file_lightning_proto_rawDescGZIP(), []int{155} } func (x *DebugLevelResponse) GetSubSystems() string { @@ -14378,7 +14552,7 @@ type PayReqString struct { func (x *PayReqString) Reset() { *x = PayReqString{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[155] + mi := &file_lightning_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14391,7 +14565,7 @@ func (x *PayReqString) String() string { func (*PayReqString) ProtoMessage() {} func (x *PayReqString) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[155] + mi := &file_lightning_proto_msgTypes[156] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14404,7 +14578,7 @@ func (x *PayReqString) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReqString.ProtoReflect.Descriptor instead. func (*PayReqString) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{155} + return file_lightning_proto_rawDescGZIP(), []int{156} } func (x *PayReqString) GetPayReq() string { @@ -14438,7 +14612,7 @@ type PayReq struct { func (x *PayReq) Reset() { *x = PayReq{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[156] + mi := &file_lightning_proto_msgTypes[157] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14451,7 +14625,7 @@ func (x *PayReq) String() string { func (*PayReq) ProtoMessage() {} func (x *PayReq) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[156] + mi := &file_lightning_proto_msgTypes[157] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14464,7 +14638,7 @@ func (x *PayReq) ProtoReflect() protoreflect.Message { // Deprecated: Use PayReq.ProtoReflect.Descriptor instead. func (*PayReq) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{156} + return file_lightning_proto_rawDescGZIP(), []int{157} } func (x *PayReq) GetDestination() string { @@ -14578,7 +14752,7 @@ type Feature struct { func (x *Feature) Reset() { *x = Feature{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[157] + mi := &file_lightning_proto_msgTypes[158] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14591,7 +14765,7 @@ func (x *Feature) String() string { func (*Feature) ProtoMessage() {} func (x *Feature) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[157] + mi := &file_lightning_proto_msgTypes[158] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14604,7 +14778,7 @@ func (x *Feature) ProtoReflect() protoreflect.Message { // Deprecated: Use Feature.ProtoReflect.Descriptor instead. func (*Feature) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{157} + return file_lightning_proto_rawDescGZIP(), []int{158} } func (x *Feature) GetName() string { @@ -14637,7 +14811,7 @@ type FeeReportRequest struct { func (x *FeeReportRequest) Reset() { *x = FeeReportRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[158] + mi := &file_lightning_proto_msgTypes[159] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14650,7 +14824,7 @@ func (x *FeeReportRequest) String() string { func (*FeeReportRequest) ProtoMessage() {} func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[158] + mi := &file_lightning_proto_msgTypes[159] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14663,7 +14837,7 @@ func (x *FeeReportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportRequest.ProtoReflect.Descriptor instead. func (*FeeReportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{158} + return file_lightning_proto_rawDescGZIP(), []int{159} } type ChannelFeeReport struct { @@ -14693,7 +14867,7 @@ type ChannelFeeReport struct { func (x *ChannelFeeReport) Reset() { *x = ChannelFeeReport{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[159] + mi := &file_lightning_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14706,7 +14880,7 @@ func (x *ChannelFeeReport) String() string { func (*ChannelFeeReport) ProtoMessage() {} func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[159] + mi := &file_lightning_proto_msgTypes[160] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14719,7 +14893,7 @@ func (x *ChannelFeeReport) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelFeeReport.ProtoReflect.Descriptor instead. func (*ChannelFeeReport) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{159} + return file_lightning_proto_rawDescGZIP(), []int{160} } func (x *ChannelFeeReport) GetChanId() uint64 { @@ -14793,7 +14967,7 @@ type FeeReportResponse struct { func (x *FeeReportResponse) Reset() { *x = FeeReportResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[160] + mi := &file_lightning_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14806,7 +14980,7 @@ func (x *FeeReportResponse) String() string { func (*FeeReportResponse) ProtoMessage() {} func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[160] + mi := &file_lightning_proto_msgTypes[161] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14819,7 +14993,7 @@ func (x *FeeReportResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeReportResponse.ProtoReflect.Descriptor instead. func (*FeeReportResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{160} + return file_lightning_proto_rawDescGZIP(), []int{161} } func (x *FeeReportResponse) GetChannelFees() []*ChannelFeeReport { @@ -14866,7 +15040,7 @@ type InboundFee struct { func (x *InboundFee) Reset() { *x = InboundFee{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[161] + mi := &file_lightning_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14879,7 +15053,7 @@ func (x *InboundFee) String() string { func (*InboundFee) ProtoMessage() {} func (x *InboundFee) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[161] + mi := &file_lightning_proto_msgTypes[162] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14892,7 +15066,7 @@ func (x *InboundFee) ProtoReflect() protoreflect.Message { // Deprecated: Use InboundFee.ProtoReflect.Descriptor instead. func (*InboundFee) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{161} + return file_lightning_proto_rawDescGZIP(), []int{162} } func (x *InboundFee) GetBaseFeeMsat() int32 { @@ -14944,7 +15118,7 @@ type PolicyUpdateRequest struct { func (x *PolicyUpdateRequest) Reset() { *x = PolicyUpdateRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[162] + mi := &file_lightning_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14957,7 +15131,7 @@ func (x *PolicyUpdateRequest) String() string { func (*PolicyUpdateRequest) ProtoMessage() {} func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[162] + mi := &file_lightning_proto_msgTypes[163] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14970,7 +15144,7 @@ func (x *PolicyUpdateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateRequest.ProtoReflect.Descriptor instead. func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{162} + return file_lightning_proto_rawDescGZIP(), []int{163} } func (m *PolicyUpdateRequest) GetScope() isPolicyUpdateRequest_Scope { @@ -15084,7 +15258,7 @@ type FailedUpdate struct { func (x *FailedUpdate) Reset() { *x = FailedUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[163] + mi := &file_lightning_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15097,7 +15271,7 @@ func (x *FailedUpdate) String() string { func (*FailedUpdate) ProtoMessage() {} func (x *FailedUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[163] + mi := &file_lightning_proto_msgTypes[164] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15110,7 +15284,7 @@ func (x *FailedUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use FailedUpdate.ProtoReflect.Descriptor instead. func (*FailedUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{163} + return file_lightning_proto_rawDescGZIP(), []int{164} } func (x *FailedUpdate) GetOutpoint() *OutPoint { @@ -15146,7 +15320,7 @@ type PolicyUpdateResponse struct { func (x *PolicyUpdateResponse) Reset() { *x = PolicyUpdateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[164] + mi := &file_lightning_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15159,7 +15333,7 @@ func (x *PolicyUpdateResponse) String() string { func (*PolicyUpdateResponse) ProtoMessage() {} func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[164] + mi := &file_lightning_proto_msgTypes[165] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15172,7 +15346,7 @@ func (x *PolicyUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyUpdateResponse.ProtoReflect.Descriptor instead. func (*PolicyUpdateResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{164} + return file_lightning_proto_rawDescGZIP(), []int{165} } func (x *PolicyUpdateResponse) GetFailedUpdates() []*FailedUpdate { @@ -15209,7 +15383,7 @@ type ForwardingHistoryRequest struct { func (x *ForwardingHistoryRequest) Reset() { *x = ForwardingHistoryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[165] + mi := &file_lightning_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15222,7 +15396,7 @@ func (x *ForwardingHistoryRequest) String() string { func (*ForwardingHistoryRequest) ProtoMessage() {} func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[165] + mi := &file_lightning_proto_msgTypes[166] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15235,7 +15409,7 @@ func (x *ForwardingHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryRequest.ProtoReflect.Descriptor instead. func (*ForwardingHistoryRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{165} + return file_lightning_proto_rawDescGZIP(), []int{166} } func (x *ForwardingHistoryRequest) GetStartTime() uint64 { @@ -15316,7 +15490,7 @@ type ForwardingEvent struct { func (x *ForwardingEvent) Reset() { *x = ForwardingEvent{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[166] + mi := &file_lightning_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15329,7 +15503,7 @@ func (x *ForwardingEvent) String() string { func (*ForwardingEvent) ProtoMessage() {} func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[166] + mi := &file_lightning_proto_msgTypes[167] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15342,7 +15516,7 @@ func (x *ForwardingEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingEvent.ProtoReflect.Descriptor instead. func (*ForwardingEvent) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{166} + return file_lightning_proto_rawDescGZIP(), []int{167} } // Deprecated: Marked as deprecated in lightning.proto. @@ -15446,7 +15620,7 @@ type ForwardingHistoryResponse struct { func (x *ForwardingHistoryResponse) Reset() { *x = ForwardingHistoryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[167] + mi := &file_lightning_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15459,7 +15633,7 @@ func (x *ForwardingHistoryResponse) String() string { func (*ForwardingHistoryResponse) ProtoMessage() {} func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[167] + mi := &file_lightning_proto_msgTypes[168] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15472,7 +15646,7 @@ func (x *ForwardingHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardingHistoryResponse.ProtoReflect.Descriptor instead. func (*ForwardingHistoryResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{167} + return file_lightning_proto_rawDescGZIP(), []int{168} } func (x *ForwardingHistoryResponse) GetForwardingEvents() []*ForwardingEvent { @@ -15501,7 +15675,7 @@ type ExportChannelBackupRequest struct { func (x *ExportChannelBackupRequest) Reset() { *x = ExportChannelBackupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[168] + mi := &file_lightning_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15514,7 +15688,7 @@ func (x *ExportChannelBackupRequest) String() string { func (*ExportChannelBackupRequest) ProtoMessage() {} func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[168] + mi := &file_lightning_proto_msgTypes[169] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15527,7 +15701,7 @@ func (x *ExportChannelBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExportChannelBackupRequest.ProtoReflect.Descriptor instead. func (*ExportChannelBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{168} + return file_lightning_proto_rawDescGZIP(), []int{169} } func (x *ExportChannelBackupRequest) GetChanPoint() *ChannelPoint { @@ -15554,7 +15728,7 @@ type ChannelBackup struct { func (x *ChannelBackup) Reset() { *x = ChannelBackup{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[169] + mi := &file_lightning_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15567,7 +15741,7 @@ func (x *ChannelBackup) String() string { func (*ChannelBackup) ProtoMessage() {} func (x *ChannelBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[169] + mi := &file_lightning_proto_msgTypes[170] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15580,7 +15754,7 @@ func (x *ChannelBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackup.ProtoReflect.Descriptor instead. func (*ChannelBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{169} + return file_lightning_proto_rawDescGZIP(), []int{170} } func (x *ChannelBackup) GetChanPoint() *ChannelPoint { @@ -15614,7 +15788,7 @@ type MultiChanBackup struct { func (x *MultiChanBackup) Reset() { *x = MultiChanBackup{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[170] + mi := &file_lightning_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15627,7 +15801,7 @@ func (x *MultiChanBackup) String() string { func (*MultiChanBackup) ProtoMessage() {} func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[170] + mi := &file_lightning_proto_msgTypes[171] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15640,7 +15814,7 @@ func (x *MultiChanBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use MultiChanBackup.ProtoReflect.Descriptor instead. func (*MultiChanBackup) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{170} + return file_lightning_proto_rawDescGZIP(), []int{171} } func (x *MultiChanBackup) GetChanPoints() []*ChannelPoint { @@ -15666,7 +15840,7 @@ type ChanBackupExportRequest struct { func (x *ChanBackupExportRequest) Reset() { *x = ChanBackupExportRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[171] + mi := &file_lightning_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15679,7 +15853,7 @@ func (x *ChanBackupExportRequest) String() string { func (*ChanBackupExportRequest) ProtoMessage() {} func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[171] + mi := &file_lightning_proto_msgTypes[172] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15692,7 +15866,7 @@ func (x *ChanBackupExportRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupExportRequest.ProtoReflect.Descriptor instead. func (*ChanBackupExportRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{171} + return file_lightning_proto_rawDescGZIP(), []int{172} } type ChanBackupSnapshot struct { @@ -15711,7 +15885,7 @@ type ChanBackupSnapshot struct { func (x *ChanBackupSnapshot) Reset() { *x = ChanBackupSnapshot{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[172] + mi := &file_lightning_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15724,7 +15898,7 @@ func (x *ChanBackupSnapshot) String() string { func (*ChanBackupSnapshot) ProtoMessage() {} func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[172] + mi := &file_lightning_proto_msgTypes[173] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15737,7 +15911,7 @@ func (x *ChanBackupSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use ChanBackupSnapshot.ProtoReflect.Descriptor instead. func (*ChanBackupSnapshot) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{172} + return file_lightning_proto_rawDescGZIP(), []int{173} } func (x *ChanBackupSnapshot) GetSingleChanBackups() *ChannelBackups { @@ -15766,7 +15940,7 @@ type ChannelBackups struct { func (x *ChannelBackups) Reset() { *x = ChannelBackups{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[173] + mi := &file_lightning_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15779,7 +15953,7 @@ func (x *ChannelBackups) String() string { func (*ChannelBackups) ProtoMessage() {} func (x *ChannelBackups) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[173] + mi := &file_lightning_proto_msgTypes[174] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15792,7 +15966,7 @@ func (x *ChannelBackups) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackups.ProtoReflect.Descriptor instead. func (*ChannelBackups) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{173} + return file_lightning_proto_rawDescGZIP(), []int{174} } func (x *ChannelBackups) GetChanBackups() []*ChannelBackup { @@ -15817,7 +15991,7 @@ type RestoreChanBackupRequest struct { func (x *RestoreChanBackupRequest) Reset() { *x = RestoreChanBackupRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[174] + mi := &file_lightning_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15830,7 +16004,7 @@ func (x *RestoreChanBackupRequest) String() string { func (*RestoreChanBackupRequest) ProtoMessage() {} func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[174] + mi := &file_lightning_proto_msgTypes[175] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15843,7 +16017,7 @@ func (x *RestoreChanBackupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreChanBackupRequest.ProtoReflect.Descriptor instead. func (*RestoreChanBackupRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{174} + return file_lightning_proto_rawDescGZIP(), []int{175} } func (m *RestoreChanBackupRequest) GetBackup() isRestoreChanBackupRequest_Backup { @@ -15895,7 +16069,7 @@ type RestoreBackupResponse struct { func (x *RestoreBackupResponse) Reset() { *x = RestoreBackupResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[175] + mi := &file_lightning_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15908,7 +16082,7 @@ func (x *RestoreBackupResponse) String() string { func (*RestoreBackupResponse) ProtoMessage() {} func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[175] + mi := &file_lightning_proto_msgTypes[176] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15921,7 +16095,7 @@ func (x *RestoreBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreBackupResponse.ProtoReflect.Descriptor instead. func (*RestoreBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{175} + return file_lightning_proto_rawDescGZIP(), []int{176} } type ChannelBackupSubscription struct { @@ -15933,7 +16107,7 @@ type ChannelBackupSubscription struct { func (x *ChannelBackupSubscription) Reset() { *x = ChannelBackupSubscription{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[176] + mi := &file_lightning_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15946,7 +16120,7 @@ func (x *ChannelBackupSubscription) String() string { func (*ChannelBackupSubscription) ProtoMessage() {} func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[176] + mi := &file_lightning_proto_msgTypes[177] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15959,7 +16133,7 @@ func (x *ChannelBackupSubscription) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelBackupSubscription.ProtoReflect.Descriptor instead. func (*ChannelBackupSubscription) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{176} + return file_lightning_proto_rawDescGZIP(), []int{177} } type VerifyChanBackupResponse struct { @@ -15971,7 +16145,7 @@ type VerifyChanBackupResponse struct { func (x *VerifyChanBackupResponse) Reset() { *x = VerifyChanBackupResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[177] + mi := &file_lightning_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15984,7 +16158,7 @@ func (x *VerifyChanBackupResponse) String() string { func (*VerifyChanBackupResponse) ProtoMessage() {} func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[177] + mi := &file_lightning_proto_msgTypes[178] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15997,7 +16171,7 @@ func (x *VerifyChanBackupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyChanBackupResponse.ProtoReflect.Descriptor instead. func (*VerifyChanBackupResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{177} + return file_lightning_proto_rawDescGZIP(), []int{178} } type MacaroonPermission struct { @@ -16014,7 +16188,7 @@ type MacaroonPermission struct { func (x *MacaroonPermission) Reset() { *x = MacaroonPermission{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[178] + mi := &file_lightning_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16027,7 +16201,7 @@ func (x *MacaroonPermission) String() string { func (*MacaroonPermission) ProtoMessage() {} func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[178] + mi := &file_lightning_proto_msgTypes[179] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16040,7 +16214,7 @@ func (x *MacaroonPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermission.ProtoReflect.Descriptor instead. func (*MacaroonPermission) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{178} + return file_lightning_proto_rawDescGZIP(), []int{179} } func (x *MacaroonPermission) GetEntity() string { @@ -16074,7 +16248,7 @@ type BakeMacaroonRequest struct { func (x *BakeMacaroonRequest) Reset() { *x = BakeMacaroonRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[179] + mi := &file_lightning_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16087,7 +16261,7 @@ func (x *BakeMacaroonRequest) String() string { func (*BakeMacaroonRequest) ProtoMessage() {} func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[179] + mi := &file_lightning_proto_msgTypes[180] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16100,7 +16274,7 @@ func (x *BakeMacaroonRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonRequest.ProtoReflect.Descriptor instead. func (*BakeMacaroonRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{179} + return file_lightning_proto_rawDescGZIP(), []int{180} } func (x *BakeMacaroonRequest) GetPermissions() []*MacaroonPermission { @@ -16136,7 +16310,7 @@ type BakeMacaroonResponse struct { func (x *BakeMacaroonResponse) Reset() { *x = BakeMacaroonResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[180] + mi := &file_lightning_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16149,7 +16323,7 @@ func (x *BakeMacaroonResponse) String() string { func (*BakeMacaroonResponse) ProtoMessage() {} func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[180] + mi := &file_lightning_proto_msgTypes[181] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16162,7 +16336,7 @@ func (x *BakeMacaroonResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BakeMacaroonResponse.ProtoReflect.Descriptor instead. func (*BakeMacaroonResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{180} + return file_lightning_proto_rawDescGZIP(), []int{181} } func (x *BakeMacaroonResponse) GetMacaroon() string { @@ -16181,7 +16355,7 @@ type ListMacaroonIDsRequest struct { func (x *ListMacaroonIDsRequest) Reset() { *x = ListMacaroonIDsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[181] + mi := &file_lightning_proto_msgTypes[182] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16194,7 +16368,7 @@ func (x *ListMacaroonIDsRequest) String() string { func (*ListMacaroonIDsRequest) ProtoMessage() {} func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[181] + mi := &file_lightning_proto_msgTypes[182] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16207,7 +16381,7 @@ func (x *ListMacaroonIDsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsRequest.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{181} + return file_lightning_proto_rawDescGZIP(), []int{182} } type ListMacaroonIDsResponse struct { @@ -16222,7 +16396,7 @@ type ListMacaroonIDsResponse struct { func (x *ListMacaroonIDsResponse) Reset() { *x = ListMacaroonIDsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[182] + mi := &file_lightning_proto_msgTypes[183] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16235,7 +16409,7 @@ func (x *ListMacaroonIDsResponse) String() string { func (*ListMacaroonIDsResponse) ProtoMessage() {} func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[182] + mi := &file_lightning_proto_msgTypes[183] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16248,7 +16422,7 @@ func (x *ListMacaroonIDsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMacaroonIDsResponse.ProtoReflect.Descriptor instead. func (*ListMacaroonIDsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{182} + return file_lightning_proto_rawDescGZIP(), []int{183} } func (x *ListMacaroonIDsResponse) GetRootKeyIds() []uint64 { @@ -16270,7 +16444,7 @@ type DeleteMacaroonIDRequest struct { func (x *DeleteMacaroonIDRequest) Reset() { *x = DeleteMacaroonIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[183] + mi := &file_lightning_proto_msgTypes[184] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16283,7 +16457,7 @@ func (x *DeleteMacaroonIDRequest) String() string { func (*DeleteMacaroonIDRequest) ProtoMessage() {} func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[183] + mi := &file_lightning_proto_msgTypes[184] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16296,7 +16470,7 @@ func (x *DeleteMacaroonIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDRequest.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{183} + return file_lightning_proto_rawDescGZIP(), []int{184} } func (x *DeleteMacaroonIDRequest) GetRootKeyId() uint64 { @@ -16318,7 +16492,7 @@ type DeleteMacaroonIDResponse struct { func (x *DeleteMacaroonIDResponse) Reset() { *x = DeleteMacaroonIDResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[184] + mi := &file_lightning_proto_msgTypes[185] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16331,7 +16505,7 @@ func (x *DeleteMacaroonIDResponse) String() string { func (*DeleteMacaroonIDResponse) ProtoMessage() {} func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[184] + mi := &file_lightning_proto_msgTypes[185] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16344,7 +16518,7 @@ func (x *DeleteMacaroonIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMacaroonIDResponse.ProtoReflect.Descriptor instead. func (*DeleteMacaroonIDResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{184} + return file_lightning_proto_rawDescGZIP(), []int{185} } func (x *DeleteMacaroonIDResponse) GetDeleted() bool { @@ -16366,7 +16540,7 @@ type MacaroonPermissionList struct { func (x *MacaroonPermissionList) Reset() { *x = MacaroonPermissionList{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[185] + mi := &file_lightning_proto_msgTypes[186] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16379,7 +16553,7 @@ func (x *MacaroonPermissionList) String() string { func (*MacaroonPermissionList) ProtoMessage() {} func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[185] + mi := &file_lightning_proto_msgTypes[186] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16392,7 +16566,7 @@ func (x *MacaroonPermissionList) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonPermissionList.ProtoReflect.Descriptor instead. func (*MacaroonPermissionList) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{185} + return file_lightning_proto_rawDescGZIP(), []int{186} } func (x *MacaroonPermissionList) GetPermissions() []*MacaroonPermission { @@ -16411,7 +16585,7 @@ type ListPermissionsRequest struct { func (x *ListPermissionsRequest) Reset() { *x = ListPermissionsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[186] + mi := &file_lightning_proto_msgTypes[187] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16424,7 +16598,7 @@ func (x *ListPermissionsRequest) String() string { func (*ListPermissionsRequest) ProtoMessage() {} func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[186] + mi := &file_lightning_proto_msgTypes[187] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16437,7 +16611,7 @@ func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsRequest.ProtoReflect.Descriptor instead. func (*ListPermissionsRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{186} + return file_lightning_proto_rawDescGZIP(), []int{187} } type ListPermissionsResponse struct { @@ -16453,7 +16627,7 @@ type ListPermissionsResponse struct { func (x *ListPermissionsResponse) Reset() { *x = ListPermissionsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[187] + mi := &file_lightning_proto_msgTypes[188] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16466,7 +16640,7 @@ func (x *ListPermissionsResponse) String() string { func (*ListPermissionsResponse) ProtoMessage() {} func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[187] + mi := &file_lightning_proto_msgTypes[188] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16479,7 +16653,7 @@ func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPermissionsResponse.ProtoReflect.Descriptor instead. func (*ListPermissionsResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{187} + return file_lightning_proto_rawDescGZIP(), []int{188} } func (x *ListPermissionsResponse) GetMethodPermissions() map[string]*MacaroonPermissionList { @@ -16516,7 +16690,7 @@ type Failure struct { func (x *Failure) Reset() { *x = Failure{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[188] + mi := &file_lightning_proto_msgTypes[189] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16529,7 +16703,7 @@ func (x *Failure) String() string { func (*Failure) ProtoMessage() {} func (x *Failure) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[188] + mi := &file_lightning_proto_msgTypes[189] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16542,7 +16716,7 @@ func (x *Failure) ProtoReflect() protoreflect.Message { // Deprecated: Use Failure.ProtoReflect.Descriptor instead. func (*Failure) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{188} + return file_lightning_proto_rawDescGZIP(), []int{189} } func (x *Failure) GetCode() Failure_FailureCode { @@ -16656,7 +16830,7 @@ type ChannelUpdate struct { func (x *ChannelUpdate) Reset() { *x = ChannelUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[189] + mi := &file_lightning_proto_msgTypes[190] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16669,7 +16843,7 @@ func (x *ChannelUpdate) String() string { func (*ChannelUpdate) ProtoMessage() {} func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[189] + mi := &file_lightning_proto_msgTypes[190] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16682,7 +16856,7 @@ func (x *ChannelUpdate) ProtoReflect() protoreflect.Message { // Deprecated: Use ChannelUpdate.ProtoReflect.Descriptor instead. func (*ChannelUpdate) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{189} + return file_lightning_proto_rawDescGZIP(), []int{190} } func (x *ChannelUpdate) GetSignature() []byte { @@ -16782,7 +16956,7 @@ type MacaroonId struct { func (x *MacaroonId) Reset() { *x = MacaroonId{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[190] + mi := &file_lightning_proto_msgTypes[191] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16795,7 +16969,7 @@ func (x *MacaroonId) String() string { func (*MacaroonId) ProtoMessage() {} func (x *MacaroonId) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[190] + mi := &file_lightning_proto_msgTypes[191] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16808,7 +16982,7 @@ func (x *MacaroonId) ProtoReflect() protoreflect.Message { // Deprecated: Use MacaroonId.ProtoReflect.Descriptor instead. func (*MacaroonId) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{190} + return file_lightning_proto_rawDescGZIP(), []int{191} } func (x *MacaroonId) GetNonce() []byte { @@ -16844,7 +17018,7 @@ type Op struct { func (x *Op) Reset() { *x = Op{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[191] + mi := &file_lightning_proto_msgTypes[192] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16857,7 +17031,7 @@ func (x *Op) String() string { func (*Op) ProtoMessage() {} func (x *Op) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[191] + mi := &file_lightning_proto_msgTypes[192] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16870,7 +17044,7 @@ func (x *Op) ProtoReflect() protoreflect.Message { // Deprecated: Use Op.ProtoReflect.Descriptor instead. func (*Op) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{191} + return file_lightning_proto_rawDescGZIP(), []int{192} } func (x *Op) GetEntity() string { @@ -16900,7 +17074,7 @@ type CheckMacPermRequest struct { func (x *CheckMacPermRequest) Reset() { *x = CheckMacPermRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[192] + mi := &file_lightning_proto_msgTypes[193] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16913,7 +17087,7 @@ func (x *CheckMacPermRequest) String() string { func (*CheckMacPermRequest) ProtoMessage() {} func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[192] + mi := &file_lightning_proto_msgTypes[193] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16926,7 +17100,7 @@ func (x *CheckMacPermRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermRequest.ProtoReflect.Descriptor instead. func (*CheckMacPermRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{192} + return file_lightning_proto_rawDescGZIP(), []int{193} } func (x *CheckMacPermRequest) GetMacaroon() []byte { @@ -16961,7 +17135,7 @@ type CheckMacPermResponse struct { func (x *CheckMacPermResponse) Reset() { *x = CheckMacPermResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[193] + mi := &file_lightning_proto_msgTypes[194] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -16974,7 +17148,7 @@ func (x *CheckMacPermResponse) String() string { func (*CheckMacPermResponse) ProtoMessage() {} func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[193] + mi := &file_lightning_proto_msgTypes[194] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -16987,7 +17161,7 @@ func (x *CheckMacPermResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckMacPermResponse.ProtoReflect.Descriptor instead. func (*CheckMacPermResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{193} + return file_lightning_proto_rawDescGZIP(), []int{194} } func (x *CheckMacPermResponse) GetValid() bool { @@ -17041,7 +17215,7 @@ type RPCMiddlewareRequest struct { func (x *RPCMiddlewareRequest) Reset() { *x = RPCMiddlewareRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[194] + mi := &file_lightning_proto_msgTypes[195] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17054,7 +17228,7 @@ func (x *RPCMiddlewareRequest) String() string { func (*RPCMiddlewareRequest) ProtoMessage() {} func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[194] + mi := &file_lightning_proto_msgTypes[195] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17067,7 +17241,7 @@ func (x *RPCMiddlewareRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareRequest.ProtoReflect.Descriptor instead. func (*RPCMiddlewareRequest) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{194} + return file_lightning_proto_rawDescGZIP(), []int{195} } func (x *RPCMiddlewareRequest) GetRequestId() uint64 { @@ -17195,7 +17369,7 @@ type StreamAuth struct { func (x *StreamAuth) Reset() { *x = StreamAuth{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[195] + mi := &file_lightning_proto_msgTypes[196] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17208,7 +17382,7 @@ func (x *StreamAuth) String() string { func (*StreamAuth) ProtoMessage() {} func (x *StreamAuth) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[195] + mi := &file_lightning_proto_msgTypes[196] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17221,7 +17395,7 @@ func (x *StreamAuth) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamAuth.ProtoReflect.Descriptor instead. func (*StreamAuth) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{195} + return file_lightning_proto_rawDescGZIP(), []int{196} } func (x *StreamAuth) GetMethodFullUri() string { @@ -17258,7 +17432,7 @@ type RPCMessage struct { func (x *RPCMessage) Reset() { *x = RPCMessage{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[196] + mi := &file_lightning_proto_msgTypes[197] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17271,7 +17445,7 @@ func (x *RPCMessage) String() string { func (*RPCMessage) ProtoMessage() {} func (x *RPCMessage) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[196] + mi := &file_lightning_proto_msgTypes[197] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17284,7 +17458,7 @@ func (x *RPCMessage) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMessage.ProtoReflect.Descriptor instead. func (*RPCMessage) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{196} + return file_lightning_proto_rawDescGZIP(), []int{197} } func (x *RPCMessage) GetMethodFullUri() string { @@ -17345,7 +17519,7 @@ type RPCMiddlewareResponse struct { func (x *RPCMiddlewareResponse) Reset() { *x = RPCMiddlewareResponse{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[197] + mi := &file_lightning_proto_msgTypes[198] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17358,7 +17532,7 @@ func (x *RPCMiddlewareResponse) String() string { func (*RPCMiddlewareResponse) ProtoMessage() {} func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[197] + mi := &file_lightning_proto_msgTypes[198] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17371,7 +17545,7 @@ func (x *RPCMiddlewareResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RPCMiddlewareResponse.ProtoReflect.Descriptor instead. func (*RPCMiddlewareResponse) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{197} + return file_lightning_proto_rawDescGZIP(), []int{198} } func (x *RPCMiddlewareResponse) GetRefMsgId() uint64 { @@ -17457,7 +17631,7 @@ type MiddlewareRegistration struct { func (x *MiddlewareRegistration) Reset() { *x = MiddlewareRegistration{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[198] + mi := &file_lightning_proto_msgTypes[199] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17470,7 +17644,7 @@ func (x *MiddlewareRegistration) String() string { func (*MiddlewareRegistration) ProtoMessage() {} func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[198] + mi := &file_lightning_proto_msgTypes[199] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17483,7 +17657,7 @@ func (x *MiddlewareRegistration) ProtoReflect() protoreflect.Message { // Deprecated: Use MiddlewareRegistration.ProtoReflect.Descriptor instead. func (*MiddlewareRegistration) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{198} + return file_lightning_proto_rawDescGZIP(), []int{199} } func (x *MiddlewareRegistration) GetMiddlewareName() string { @@ -17530,7 +17704,7 @@ type InterceptFeedback struct { func (x *InterceptFeedback) Reset() { *x = InterceptFeedback{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[199] + mi := &file_lightning_proto_msgTypes[200] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17543,7 +17717,7 @@ func (x *InterceptFeedback) String() string { func (*InterceptFeedback) ProtoMessage() {} func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[199] + mi := &file_lightning_proto_msgTypes[200] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17556,7 +17730,7 @@ func (x *InterceptFeedback) ProtoReflect() protoreflect.Message { // Deprecated: Use InterceptFeedback.ProtoReflect.Descriptor instead. func (*InterceptFeedback) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{199} + return file_lightning_proto_rawDescGZIP(), []int{200} } func (x *InterceptFeedback) GetError() string { @@ -17610,12 +17784,14 @@ type PendingChannelsResponse_PendingChannel struct { // useful information. This is only ever stored locally and in no way // impacts the channel's operation. Memo string `protobuf:"bytes,13,opt,name=memo,proto3" json:"memo,omitempty"` + // Custom channel data that might be populated in custom channels. + CustomChannelData []byte `protobuf:"bytes,34,opt,name=custom_channel_data,json=customChannelData,proto3" json:"custom_channel_data,omitempty"` } func (x *PendingChannelsResponse_PendingChannel) Reset() { *x = PendingChannelsResponse_PendingChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[206] + mi := &file_lightning_proto_msgTypes[207] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17628,7 +17804,7 @@ func (x *PendingChannelsResponse_PendingChannel) String() string { func (*PendingChannelsResponse_PendingChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[206] + mi := &file_lightning_proto_msgTypes[207] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17641,7 +17817,7 @@ func (x *PendingChannelsResponse_PendingChannel) ProtoReflect() protoreflect.Mes // Deprecated: Use PendingChannelsResponse_PendingChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 0} + return file_lightning_proto_rawDescGZIP(), []int{90, 0} } func (x *PendingChannelsResponse_PendingChannel) GetRemoteNodePub() string { @@ -17735,6 +17911,13 @@ func (x *PendingChannelsResponse_PendingChannel) GetMemo() string { return "" } +func (x *PendingChannelsResponse_PendingChannel) GetCustomChannelData() []byte { + if x != nil { + return x.CustomChannelData + } + return nil +} + type PendingChannelsResponse_PendingOpenChannel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -17769,7 +17952,7 @@ type PendingChannelsResponse_PendingOpenChannel struct { func (x *PendingChannelsResponse_PendingOpenChannel) Reset() { *x = PendingChannelsResponse_PendingOpenChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[207] + mi := &file_lightning_proto_msgTypes[208] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17782,7 +17965,7 @@ func (x *PendingChannelsResponse_PendingOpenChannel) String() string { func (*PendingChannelsResponse_PendingOpenChannel) ProtoMessage() {} func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[207] + mi := &file_lightning_proto_msgTypes[208] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17795,7 +17978,7 @@ func (x *PendingChannelsResponse_PendingOpenChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_PendingOpenChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_PendingOpenChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 1} + return file_lightning_proto_rawDescGZIP(), []int{90, 1} } func (x *PendingChannelsResponse_PendingOpenChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -17855,7 +18038,7 @@ type PendingChannelsResponse_WaitingCloseChannel struct { func (x *PendingChannelsResponse_WaitingCloseChannel) Reset() { *x = PendingChannelsResponse_WaitingCloseChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[208] + mi := &file_lightning_proto_msgTypes[209] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17868,7 +18051,7 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) String() string { func (*PendingChannelsResponse_WaitingCloseChannel) ProtoMessage() {} func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[208] + mi := &file_lightning_proto_msgTypes[209] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17881,7 +18064,7 @@ func (x *PendingChannelsResponse_WaitingCloseChannel) ProtoReflect() protoreflec // Deprecated: Use PendingChannelsResponse_WaitingCloseChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_WaitingCloseChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 2} + return file_lightning_proto_rawDescGZIP(), []int{90, 2} } func (x *PendingChannelsResponse_WaitingCloseChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -17944,7 +18127,7 @@ type PendingChannelsResponse_Commitments struct { func (x *PendingChannelsResponse_Commitments) Reset() { *x = PendingChannelsResponse_Commitments{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[209] + mi := &file_lightning_proto_msgTypes[210] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -17957,7 +18140,7 @@ func (x *PendingChannelsResponse_Commitments) String() string { func (*PendingChannelsResponse_Commitments) ProtoMessage() {} func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[209] + mi := &file_lightning_proto_msgTypes[210] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -17970,7 +18153,7 @@ func (x *PendingChannelsResponse_Commitments) ProtoReflect() protoreflect.Messag // Deprecated: Use PendingChannelsResponse_Commitments.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_Commitments) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 3} + return file_lightning_proto_rawDescGZIP(), []int{90, 3} } func (x *PendingChannelsResponse_Commitments) GetLocalTxid() string { @@ -18029,7 +18212,7 @@ type PendingChannelsResponse_ClosedChannel struct { func (x *PendingChannelsResponse_ClosedChannel) Reset() { *x = PendingChannelsResponse_ClosedChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[210] + mi := &file_lightning_proto_msgTypes[211] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18042,7 +18225,7 @@ func (x *PendingChannelsResponse_ClosedChannel) String() string { func (*PendingChannelsResponse_ClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[210] + mi := &file_lightning_proto_msgTypes[211] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18055,7 +18238,7 @@ func (x *PendingChannelsResponse_ClosedChannel) ProtoReflect() protoreflect.Mess // Deprecated: Use PendingChannelsResponse_ClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 4} + return file_lightning_proto_rawDescGZIP(), []int{90, 4} } func (x *PendingChannelsResponse_ClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18098,7 +18281,7 @@ type PendingChannelsResponse_ForceClosedChannel struct { func (x *PendingChannelsResponse_ForceClosedChannel) Reset() { *x = PendingChannelsResponse_ForceClosedChannel{} if protoimpl.UnsafeEnabled { - mi := &file_lightning_proto_msgTypes[211] + mi := &file_lightning_proto_msgTypes[212] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -18111,7 +18294,7 @@ func (x *PendingChannelsResponse_ForceClosedChannel) String() string { func (*PendingChannelsResponse_ForceClosedChannel) ProtoMessage() {} func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect.Message { - mi := &file_lightning_proto_msgTypes[211] + mi := &file_lightning_proto_msgTypes[212] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -18124,7 +18307,7 @@ func (x *PendingChannelsResponse_ForceClosedChannel) ProtoReflect() protoreflect // Deprecated: Use PendingChannelsResponse_ForceClosedChannel.ProtoReflect.Descriptor instead. func (*PendingChannelsResponse_ForceClosedChannel) Descriptor() ([]byte, []int) { - return file_lightning_proto_rawDescGZIP(), []int{89, 5} + return file_lightning_proto_rawDescGZIP(), []int{90, 5} } func (x *PendingChannelsResponse_ForceClosedChannel) GetChannel() *PendingChannelsResponse_PendingChannel { @@ -18620,7 +18803,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x61, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, - 0x22, 0xad, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, + 0x22, 0xdd, 0x0b, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x6d, @@ -18711,6 +18894,9 @@ var file_lightning_proto_rawDesc = []byte{ 0x69, 0x61, 0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x53, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, + 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, @@ -18983,2290 +19169,2340 @@ var file_lightning_proto_rawDesc = []byte{ 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x51, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xbf, 0x02, 0x0a, 0x13, 0x43, 0x6c, - 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x61, - 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x6c, 0x69, - 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, - 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, - 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x66, - 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, - 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x11, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3a, - 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, - 0x6f, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, - 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x79, 0x0a, 0x13, 0x52, 0x65, - 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x75, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xc9, 0x02, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, - 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x33, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, - 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, - 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, - 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x17, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x22, 0x89, 0x06, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, - 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, - 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, - 0x68, 0x53, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, - 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, 0x0d, - 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, 0x6e, - 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, - 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, - 0x63, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x1b, 0x0a, 0x09, - 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x69, - 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, - 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, - 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, - 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, - 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, - 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x61, - 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, - 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, - 0x6f, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x5b, 0x0a, - 0x18, 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, 0x3f, 0x0a, 0x10, 0x70, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xcb, 0x08, 0x0a, 0x12, 0x4f, - 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, - 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, - 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, - 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x5f, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x53, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x2e, 0x0a, + 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x9a, 0x02, + 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, + 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x12, 0x40, 0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x41, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0xbf, 0x02, 0x0a, 0x13, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, + 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, + 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x6c, + 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, + 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x56, 0x62, + 0x79, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x6f, 0x57, 0x61, 0x69, 0x74, 0x22, 0xd3, 0x01, 0x0a, + 0x11, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, + 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, + 0x3a, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x63, + 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x46, 0x0a, 0x0d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x79, 0x0a, 0x13, 0x52, + 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x73, 0x62, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x70, 0x73, 0x62, 0x74, 0x22, 0xc9, 0x02, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, + 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x54, 0x0a, 0x17, + 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x69, + 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x22, 0x89, 0x06, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, + 0x64, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, - 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, - 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, - 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, - 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, - 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, - 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, - 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, - 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x70, - 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x23, - 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x35, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, - 0x68, 0x69, 0x6d, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, 0x66, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, + 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, + 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, + 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x1f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x69, - 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, 0x20, + 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, - 0x6c, 0x63, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x6c, 0x63, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, - 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, 0x0a, - 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, + 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x1b, 0x0a, + 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, - 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, - 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x16, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, - 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, + 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, - 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x19, + 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x75, - 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, 0x75, - 0x6e, 0x64, 0x4d, 0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x1b, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x6f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, - 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x6e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, 0x65, - 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, - 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0f, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x49, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x48, - 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, - 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, - 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, 0x77, - 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, 0x0a, - 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, - 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x88, 0x02, 0x0a, 0x0d, 0x43, 0x68, - 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x61, - 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, 0x0a, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, 0x75, - 0x73, 0x69, 0x67, 0x32, 0x22, 0x6e, 0x0a, 0x08, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, - 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, 0x73, - 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x73, 0x68, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x68, 0x69, 0x6d, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, - 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x53, 0x68, 0x69, 0x6d, 0x12, 0x2e, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x73, 0x68, 0x69, - 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, - 0x53, 0x68, 0x69, 0x6d, 0x42, 0x06, 0x0a, 0x04, 0x73, 0x68, 0x69, 0x6d, 0x22, 0x3b, 0x0a, 0x11, - 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, + 0x6d, 0x6f, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x22, 0x5b, + 0x0a, 0x18, 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, 0x3f, 0x0a, 0x10, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xcb, 0x08, 0x0a, 0x12, + 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, + 0x79, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x50, 0x65, + 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6e, 0x6f, 0x64, + 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, + 0x75, 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, + 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x61, 0x74, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x0a, 0x73, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x73, 0x76, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, + 0x66, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x75, 0x6e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x35, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x73, 0x68, 0x69, 0x6d, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x52, 0x0b, + 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x43, 0x0a, 0x1f, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, + 0x69, 0x6e, 0x5f, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x61, 0x78, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x49, 0x6e, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x68, + 0x74, 0x6c, 0x63, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x4d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, + 0x78, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x73, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x73, 0x76, 0x12, 0x3e, + 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x13, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x63, 0x69, 0x64, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x73, 0x63, 0x69, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, + 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x42, 0x61, 0x73, 0x65, 0x46, + 0x65, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x46, 0x65, 0x65, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x19, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, + 0x75, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x66, + 0x75, 0x6e, 0x64, 0x4d, 0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x1b, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x6f, 0x75, + 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, + 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x10, 0x4f, 0x70, + 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, + 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, + 0x61, 0x6e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x4f, 0x70, + 0x65, 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72, 0x50, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x12, 0x26, 0x0a, + 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, + 0x48, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, + 0x0a, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5f, 0x0a, 0x0d, 0x4b, 0x65, 0x79, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x61, + 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2a, + 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x22, 0x88, 0x02, 0x0a, 0x0d, 0x43, + 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x10, 0x0a, 0x03, + 0x61, 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x32, + 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, + 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x68, 0x61, 0x77, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0a, 0x74, 0x68, 0x61, 0x77, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6d, + 0x75, 0x73, 0x69, 0x67, 0x32, 0x22, 0x6e, 0x0a, 0x08, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, + 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x46, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, - 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, - 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, - 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x22, 0x80, 0x01, - 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, - 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x20, - 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x77, 0x54, 0x78, - 0x22, 0x99, 0x02, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x68, 0x69, - 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x6d, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x63, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, 0x61, + 0x73, 0x65, 0x50, 0x73, 0x62, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x53, 0x68, 0x69, 0x6d, 0x12, 0x2e, 0x0a, 0x09, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x73, 0x68, + 0x69, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x73, 0x62, 0x74, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x08, 0x70, 0x73, 0x62, + 0x74, 0x53, 0x68, 0x69, 0x6d, 0x42, 0x06, 0x0a, 0x04, 0x73, 0x68, 0x69, 0x6d, 0x22, 0x3b, 0x0a, + 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, - 0x48, 0x00, 0x52, 0x0a, 0x70, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x41, - 0x0a, 0x0d, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x42, 0x09, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, 0x16, 0x0a, 0x14, - 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x22, 0xcc, 0x01, 0x0a, 0x0b, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, - 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x22, 0x3e, 0x0a, 0x16, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, - 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x61, - 0x77, 0x54, 0x78, 0x22, 0xe1, 0x13, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x65, 0x0a, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, - 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, 0x2e, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 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, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x6f, - 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x2e, 0x46, 0x6f, 0x72, 0x63, 0x65, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x1b, 0x70, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, 0x77, 0x61, - 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 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, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x14, - 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0xb3, 0x04, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, 0x12, - 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, - 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x16, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, - 0x74, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, 0x6d, 0x5f, - 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, - 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, - 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, 0x61, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x1a, 0xf9, 0x01, 0x0a, 0x12, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x73, 0x62, + 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, + 0x70, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x22, 0x80, + 0x01, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x50, 0x73, 0x62, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x77, 0x54, + 0x78, 0x22, 0x99, 0x02, 0x0a, 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x68, + 0x69, 0x6d, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x68, 0x69, 0x6d, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x0b, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x68, 0x69, 0x6d, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x68, 0x69, 0x6d, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x73, 0x62, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, + 0x41, 0x0a, 0x0d, 0x70, 0x73, 0x62, 0x74, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x73, 0x62, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x22, 0x16, 0x0a, + 0x14, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x22, 0xcc, 0x01, 0x0a, 0x0b, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, + 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, + 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, + 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x67, 0x65, 0x22, 0x3e, 0x0a, 0x16, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, + 0x61, 0x77, 0x54, 0x78, 0x22, 0x91, 0x14, 0x0a, 0x17, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x5f, + 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x4c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x12, 0x65, 0x0a, 0x15, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x31, 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, 0x2e, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x13, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 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, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x16, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x1e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x2e, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x1b, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x68, 0x0a, 0x16, 0x77, + 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 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, 0x2e, 0x57, 0x61, 0x69, 0x74, + 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x14, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0xe3, 0x04, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x75, 0x62, + 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x33, 0x0a, + 0x16, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, + 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x14, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x52, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x09, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x09, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6e, 0x75, 0x6d, + 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x75, 0x6d, 0x46, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x68, + 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x1a, 0xf9, 0x01, 0x0a, 0x12, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x1c, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x32, 0x0a, + 0x15, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x1a, 0x9a, 0x02, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, + 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 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, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, + 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, + 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 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, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, - 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6b, 0x77, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4b, 0x77, 0x12, 0x32, 0x0a, 0x15, - 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x66, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x1a, 0x9a, 0x02, 0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, + 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, + 0x78, 0x48, 0x65, 0x78, 0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, + 0x78, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x78, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x54, 0x78, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x54, 0x78, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, + 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, 0x7b, 0x0a, 0x0d, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, + 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, + 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x63, + 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, 0x62, 0x6f, - 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x0b, - 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2a, 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, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0b, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, - 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x24, 0x0a, - 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x65, 0x78, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, - 0x48, 0x65, 0x78, 0x1a, 0xa3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x78, - 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, - 0x78, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, - 0x78, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x46, 0x65, - 0x65, 0x53, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x1d, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x46, 0x65, 0x65, 0x53, 0x61, 0x74, 0x1a, 0x7b, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, - 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, - 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x1a, 0xee, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x63, 0x65, - 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, - 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, + 0x6d, 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, + 0x4d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, + 0x52, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x55, + 0x0a, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 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, 0x2e, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x07, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, - 0x67, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, - 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x6d, - 0x62, 0x6f, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x6c, 0x69, 0x6d, 0x62, 0x6f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x27, - 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x5f, 0x74, 0x69, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x54, 0x69, 0x6c, 0x4d, - 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x54, 0x4c, 0x43, 0x52, - 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x55, 0x0a, - 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 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, 0x2e, 0x46, 0x6f, - 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x2e, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x61, 0x6e, - 0x63, 0x68, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, 0x42, 0x4f, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x52, 0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, - 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x04, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x6f, 0x70, - 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x43, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x12, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x4b, - 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, - 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 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, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, - 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, - 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x43, - 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x02, 0x12, 0x14, - 0x0a, 0x10, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, - 0x45, 0x4c, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x5f, - 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x04, 0x12, 0x1a, - 0x0a, 0x16, 0x46, 0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, 0x44, - 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, 0x42, 0x09, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, - 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, - 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x4d, 0x0a, 0x14, 0x57, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x22, 0xbd, 0x03, 0x0a, 0x15, 0x57, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, - 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3f, - 0x0a, 0x1c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x12, - 0x59, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 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, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, - 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x80, 0x04, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, - 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x14, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x12, 0x32, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x17, - 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, 0x6e, - 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x18, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, - 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x16, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x52, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x1a, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x17, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, 0x61, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, - 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x9a, 0x07, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, - 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, - 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, - 0x09, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, - 0x74, 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, - 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0c, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, - 0x12, 0x3b, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, 0x65, - 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, 0x52, - 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, 0x0a, - 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, 0x62, - 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x11, 0x75, 0x73, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x70, - 0x61, 0x69, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, 0x6e, - 0x6f, 0x72, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x74, - 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, - 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, - 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, - 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 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, - 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, - 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, - 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, - 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, - 0x6e, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x15, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x13, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, - 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, 0x62, - 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, - 0x68, 0x73, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, 0x65, - 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, - 0x69, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, + 0x6f, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x2e, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x61, + 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x22, 0x31, 0x0a, 0x0b, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4d, 0x42, 0x4f, 0x10, 0x00, 0x12, + 0x0d, 0x0a, 0x09, 0x52, 0x45, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x08, + 0x0a, 0x04, 0x4c, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x1a, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xff, 0x04, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x0c, 0x6f, + 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x48, 0x00, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x12, 0x43, 0x0a, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x40, 0x0a, 0x10, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x48, 0x0a, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x12, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x4b, 0x0a, 0x16, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, + 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x52, 0x65, 0x73, + 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 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, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, + 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4c, 0x4f, 0x53, 0x45, + 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x41, + 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x02, 0x12, + 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, + 0x4e, 0x45, 0x4c, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x04, 0x12, + 0x1a, 0x0a, 0x16, 0x46, 0x55, 0x4c, 0x4c, 0x59, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x4c, 0x56, 0x45, + 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x10, 0x05, 0x42, 0x09, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x14, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, + 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, + 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x4d, 0x0a, 0x14, + 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x73, 0x22, 0xbd, 0x03, 0x0a, 0x15, + 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x75, 0x6e, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, + 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x3f, 0x0a, 0x1c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x43, 0x68, 0x61, 0x6e, + 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 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, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x1a, 0x5e, 0x0a, 0x13, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x06, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x03, 0x73, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x6d, 0x73, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0xb0, 0x04, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1c, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, + 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x12, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x0d, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, + 0x17, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x15, 0x75, + 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x18, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x64, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x16, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, + 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4a, 0x0a, + 0x1a, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x52, 0x17, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x4c, 0x0a, 0x1b, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x18, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x9a, 0x07, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, + 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, + 0x0a, 0x09, 0x66, 0x65, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, + 0x73, 0x12, 0x3b, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x64, 0x67, + 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x24, + 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x75, + 0x62, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x11, 0x75, 0x73, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x5f, + 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0c, 0x69, 0x67, + 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, + 0x74, 0x76, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, + 0x63, 0x6c, 0x74, 0x76, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x60, 0x0a, 0x13, 0x64, 0x65, 0x73, + 0x74, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 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, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x43, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x6f, + 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, + 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, + 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, + 0x69, 0x6e, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x15, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x13, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, + 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x13, + 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x12, 0x36, 0x0a, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x0c, 0x64, + 0x65, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, + 0x74, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, + 0x08, 0x03, 0x10, 0x04, 0x22, 0x2e, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, + 0x72, 0x6f, 0x62, 0x22, 0xa5, 0x05, 0x0a, 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x28, 0x0a, 0x0e, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, + 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, + 0x65, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, + 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, + 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, + 0x74, 0x6c, 0x76, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x74, 0x6c, 0x76, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x70, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, + 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x6d, 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x61, 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x12, 0x44, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, + 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x09, 0x4d, + 0x50, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, + 0x74, 0x22, 0x62, 0x0a, 0x09, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, + 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, + 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xc4, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, + 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, + 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, + 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x68, + 0x6f, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x52, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, + 0x6f, 0x70, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x13, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x22, 0x55, 0x0a, 0x0f, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x22, 0xae, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x28, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, + 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x61, 0x70, 0x61, + 0x63, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x22, 0xc6, 0x03, 0x0a, 0x0d, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x3e, + 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x4e, + 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4b, + 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, - 0x03, 0x10, 0x04, 0x22, 0x2e, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, - 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, - 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x02, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, - 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x10, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x06, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, - 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x72, - 0x6f, 0x62, 0x22, 0xa5, 0x05, 0x0a, 0x03, 0x48, 0x6f, 0x70, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, - 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, - 0x18, 0x01, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, - 0x12, 0x28, 0x0a, 0x0e, 0x61, 0x6d, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x61, 0x6d, - 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x2d, 0x0a, 0x13, 0x61, 0x6d, 0x74, 0x5f, - 0x74, 0x6f, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x61, 0x6d, 0x74, 0x54, 0x6f, 0x46, 0x6f, 0x72, 0x77, - 0x61, 0x72, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x74, - 0x6c, 0x76, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, - 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x74, 0x6c, 0x76, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x70, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x50, 0x50, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x6d, 0x70, 0x70, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x61, 0x6d, 0x70, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, - 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x61, 0x6d, 0x70, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x12, 0x44, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, - 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, - 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x09, 0x4d, 0x50, - 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, - 0x22, 0x62, 0x0a, 0x09, 0x41, 0x4d, 0x50, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, - 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, - 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x22, 0xe1, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, - 0x0a, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, - 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, - 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x21, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x66, 0x65, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x09, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, - 0x52, 0x08, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x6f, - 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x48, 0x6f, 0x70, 0x52, 0x04, 0x68, 0x6f, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x55, 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, - 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, - 0x62, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x22, - 0xae, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x28, 0x0a, 0x04, - 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, - 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, - 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, - 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x22, 0xc6, 0x03, 0x0a, 0x0d, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, - 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, - 0x61, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x66, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6c, + 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, + 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x89, 0x04, 0x0a, 0x0d, 0x52, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, + 0x65, 0x6c, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, + 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, + 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, + 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x4e, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x61, + 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3c, 0x0a, 0x1b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, + 0x4d, 0x73, 0x61, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x03, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, + 0x64, 0x65, 0x32, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, + 0x6f, 0x64, 0x65, 0x32, 0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, + 0x69, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, + 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, + 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, + 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x46, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, + 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x55, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x64, 0x0a, + 0x0c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2a, 0x0a, + 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, - 0x64, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x0e, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, - 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0b, 0x4e, 0x6f, 0x64, - 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x22, 0x89, 0x04, 0x0a, 0x0d, 0x52, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, - 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x12, 0x22, 0x0a, 0x0d, 0x66, - 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x2d, 0x0a, 0x13, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, - 0x69, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x65, - 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, - 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, - 0x4e, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x43, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, - 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x62, - 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, - 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x3c, 0x0a, 0x1b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x5f, 0x6d, 0x73, 0x61, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x4d, 0x73, 0x61, 0x74, - 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, + 0x67, 0x65, 0x73, 0x22, 0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, + 0x0a, 0x16, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x65, + 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, + 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, 0x2e, 0x42, 0x65, 0x74, 0x77, 0x65, + 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, + 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, 0x5c, 0x0a, 0x1a, + 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x0b, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, 0x0a, 0x0f, 0x43, 0x68, + 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, + 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0xd5, 0x03, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, 0x75, + 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, + 0x61, 0x76, 0x67, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0e, + 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, + 0x65, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x0e, 0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, + 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x69, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x61, + 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, + 0x10, 0x6e, 0x75, 0x6d, 0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, 0x62, + 0x69, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 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, 0x22, 0xcd, 0x01, 0x0a, 0x13, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, + 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x6e, + 0x6f, 0x64, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x20, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x0f, 0x67, 0x6c, 0x6f, 0x62, 0x61, + 0x6c, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x12, 0x39, 0x0a, 0x0e, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0d, 0x6e, 0x6f, + 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, + 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x91, 0x02, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, + 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, + 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, + 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x13, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x07, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x12, + 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, + 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x65, + 0x65, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x66, 0x65, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x69, + 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, + 0x66, 0x65, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, + 0x69, 0x6c, 0x6c, 0x69, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x74, + 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x65, 0x74, 0x49, 0x44, 0x12, 0x15, + 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x73, 0x65, 0x74, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, + 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x70, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, + 0x70, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x68, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x22, + 0xc4, 0x02, 0x0a, 0x12, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x35, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, + 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x22, 0x0a, + 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, + 0x74, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x13, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x65, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, + 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, + 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63, + 0x4d, 0x61, 0x78, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x69, 0x6e, 0x64, + 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x10, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, + 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x6c, + 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, + 0x48, 0x6f, 0x70, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x73, + 0x22, 0x56, 0x0a, 0x0a, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x12, 0x21, + 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x4e, 0x6f, 0x64, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0xa8, 0x01, 0x0a, 0x0f, 0x41, 0x4d, 0x50, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, + 0x73, 0x61, 0x74, 0x22, 0xac, 0x0a, 0x0a, 0x07, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, + 0x65, 0x6d, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x17, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, + 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x61, + 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, + 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, + 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x1d, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, + 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x73, 0x61, 0x74, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x53, + 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, + 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x15, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, + 0x63, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x05, 0x68, 0x74, + 0x6c, 0x63, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x69, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x69, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x1a, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, + 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x61, 0x6d, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x05, 0x69, 0x73, 0x41, 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x11, 0x61, 0x6d, 0x70, 0x5f, 0x69, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x1c, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x2e, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x61, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x62, 0x6c, + 0x69, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x42, + 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x48, 0x0a, 0x13, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, + 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, + 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x62, + 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, + 0x14, 0x41, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, + 0x4e, 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, 0x12, 0x0c, + 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x02, + 0x10, 0x03, 0x22, 0xef, 0x01, 0x0a, 0x11, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, + 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, + 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x4e, 0x75, 0x6d, 0x52, 0x65, 0x61, + 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, + 0x68, 0x6f, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x07, 0x6e, 0x75, + 0x6d, 0x48, 0x6f, 0x70, 0x73, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, + 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, + 0x02, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6f, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x6e, + 0x6f, 0x64, 0x65, 0x4f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x42, + 0x14, 0x0a, 0x12, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x6c, + 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, + 0x70, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, + 0x61, 0x74, 0x68, 0x73, 0x22, 0xac, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x48, 0x54, 0x4c, 0x43, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x48, 0x54, 0x4c, 0x43, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70, 0x70, 0x5f, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x70, 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x03, 0x61, 0x6d, + 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x44, 0x61, 0x74, + 0x61, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x8c, 0x01, 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, + 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a, 0x72, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x5f, 0x73, 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x08, 0x72, 0x48, 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, + 0x68, 0x22, 0xfc, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, + 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, + 0x22, 0x9b, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x55, + 0x0a, 0x13, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xcb, 0x06, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, + 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x53, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, + 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x4e, + 0x73, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, + 0x65, 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x62, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, + 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, + 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0xcc, 0x03, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, - 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x6c, - 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, - 0x65, 0x31, 0x5f, 0x70, 0x75, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, - 0x64, 0x65, 0x31, 0x50, 0x75, 0x62, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x5f, - 0x70, 0x75, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x32, - 0x50, 0x75, 0x62, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, - 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x31, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, - 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, - 0x65, 0x31, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x37, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, - 0x32, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x32, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x2e, 0x43, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, - 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x46, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, - 0x75, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x6e, - 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x2a, 0x0a, 0x05, 0x6e, 0x6f, 0x64, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, - 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x22, - 0x41, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x16, 0x62, 0x65, - 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x72, 0x61, - 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, - 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x15, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, - 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x1a, 0x5c, 0x0a, 0x1a, 0x42, 0x65, 0x74, 0x77, - 0x65, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, - 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4e, 0x0a, 0x0b, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6e, - 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, - 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xd5, 0x03, 0x0a, 0x0b, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x0a, 0x0e, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x5f, 0x64, 0x69, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x67, 0x72, 0x61, 0x70, 0x68, 0x44, 0x69, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x76, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, - 0x67, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x61, 0x76, 0x67, 0x4f, - 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, - 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x67, 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x4f, 0x75, 0x74, 0x44, 0x65, 0x67, 0x72, 0x65, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6e, - 0x75, 0x6d, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x34, - 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, - 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x61, 0x70, 0x61, - 0x63, 0x69, 0x74, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, - 0x61, 0x76, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, - 0x0a, 0x10, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, - 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, 0x6d, - 0x5f, 0x7a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x5a, 0x6f, 0x6d, 0x62, 0x69, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x73, 0x22, 0x0d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 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, 0x22, - 0xcd, 0x01, 0x0a, 0x13, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, - 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, - 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x73, 0x22, - 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x20, - 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x4b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x0f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x66, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, - 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0e, - 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0d, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x46, 0x65, 0x61, + 0x38, 0x01, 0x22, 0x59, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, + 0x54, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, + 0x0a, 0x09, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x04, 0x4a, 0x04, 0x08, + 0x04, 0x10, 0x05, 0x22, 0xd5, 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, + 0x49, 0x64, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, + 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, + 0x0f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, + 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, + 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, + 0x61, 0x67, 0x65, 0x22, 0x36, 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, + 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0xb4, 0x02, 0x0a, 0x13, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, + 0x6e, 0x64, 0x22, 0xca, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, + 0x65, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, + 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, + 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, + 0x19, 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, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x41, + 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, + 0x0a, 0x19, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x5f, 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x53, 0x68, 0x69, 0x6d, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x16, 0x69, 0x5f, 0x6b, + 0x6e, 0x6f, 0x77, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61, 0x6d, 0x5f, 0x64, 0x6f, + 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, + 0x57, 0x68, 0x61, 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x18, 0x0a, 0x16, + 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x68, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, + 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, 0x63, 0x22, 0x35, + 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0xf0, + 0x04, 0x0a, 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, + 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, + 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, + 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, + 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, + 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x75, 0x72, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, + 0x61, 0x74, 0x68, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x91, 0x02, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, - 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, - 0x61, 0x6e, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, - 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, - 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, - 0x63, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, - 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x64, 0x76, - 0x65, 0x72, 0x74, 0x69, 0x73, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6e, - 0x67, 0x4e, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, - 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, - 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, - 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x61, - 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x63, - 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, - 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, - 0xcf, 0x01, 0x0a, 0x07, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, - 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, - 0x64, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, - 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, - 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x42, 0x61, 0x73, - 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x3e, 0x0a, 0x1b, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x72, 0x6f, - 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x6f, - 0x6e, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x19, 0x66, 0x65, 0x65, 0x50, - 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x69, 0x6c, 0x6c, 0x69, - 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x79, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0f, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x44, 0x65, 0x6c, 0x74, - 0x61, 0x22, 0x1e, 0x0a, 0x05, 0x53, 0x65, 0x74, 0x49, 0x44, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, - 0x64, 0x22, 0x38, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x12, 0x2b, - 0x0a, 0x09, 0x68, 0x6f, 0x70, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x6f, 0x70, 0x48, 0x69, 0x6e, - 0x74, 0x52, 0x08, 0x68, 0x6f, 0x70, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x22, 0xc4, 0x02, 0x0a, 0x12, - 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x35, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0b, 0x62, 0x6c, - 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, - 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, - 0x15, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x70, 0x72, - 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, - 0x65, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, - 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x68, - 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x22, 0x0a, 0x0d, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, - 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x69, - 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x64, 0x65, 0x12, - 0x25, 0x0a, 0x0e, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, - 0x64, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x52, - 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x73, 0x22, 0x56, 0x0a, 0x0a, - 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x48, 0x6f, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, - 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0b, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x25, 0x0a, - 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x44, 0x61, 0x74, 0x61, 0x22, 0xa8, 0x01, 0x0a, 0x0f, 0x41, 0x4d, 0x50, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x61, - 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, 0x61, 0x74, 0x22, - 0xac, 0x0a, 0x0a, 0x07, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, - 0x65, 0x6d, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1c, 0x0a, 0x07, 0x73, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, - 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, - 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x27, - 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, - 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, - 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, - 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, - 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, - 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x11, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1d, 0x0a, - 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0c, - 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x53, 0x61, 0x74, 0x12, 0x22, - 0x0a, 0x0d, 0x61, 0x6d, 0x74, 0x5f, 0x70, 0x61, 0x69, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x74, 0x50, 0x61, 0x69, 0x64, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x16, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, - 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, - 0x6b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, - 0x73, 0x4b, 0x65, 0x79, 0x73, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x69, - 0x73, 0x5f, 0x61, 0x6d, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x41, - 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x11, 0x61, 0x6d, 0x70, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x2e, 0x41, 0x6d, - 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0f, 0x61, 0x6d, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, - 0x64, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x42, 0x6c, 0x69, 0x6e, 0x64, - 0x65, 0x64, 0x12, 0x48, 0x0a, 0x13, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, - 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x62, 0x6c, 0x69, 0x6e, 0x64, - 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x4b, 0x0a, 0x0d, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, 0x14, 0x41, 0x6d, 0x70, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 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, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, - 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xef, - 0x01, 0x0a, 0x11, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, - 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, - 0x00, 0x52, 0x0e, 0x6d, 0x69, 0x6e, 0x4e, 0x75, 0x6d, 0x52, 0x65, 0x61, 0x6c, 0x48, 0x6f, 0x70, - 0x73, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x48, 0x6f, 0x70, - 0x73, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0b, 0x6d, - 0x61, 0x78, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, - 0x12, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, - 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x6e, 0x6f, 0x64, 0x65, 0x4f, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x14, 0x0a, 0x12, 0x5f, - 0x6d, 0x69, 0x6e, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, - 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x42, 0x10, - 0x0a, 0x0e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, - 0x22, 0xfc, 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, - 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x08, - 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, - 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, - 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, - 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x6d, 0x70, 0x70, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, - 0x6d, 0x70, 0x70, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x1c, 0x0a, 0x03, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x4d, 0x50, 0x52, 0x03, 0x61, 0x6d, 0x70, 0x1a, 0x40, 0x0a, - 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x8c, 0x01, 0x0a, 0x03, 0x41, 0x4d, 0x50, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x5f, - 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x72, 0x6f, 0x6f, - 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x94, - 0x01, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0a, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x73, - 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08, 0x72, 0x48, - 0x61, 0x73, 0x68, 0x53, 0x74, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x22, 0xfc, 0x01, - 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6e, 0x75, - 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, - 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x22, 0x9b, 0x01, 0x0a, - 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, - 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x12, - 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x55, 0x0a, 0x13, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x61, 0x64, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x22, 0x9d, 0x05, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, - 0x12, 0x18, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, - 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x42, 0x02, 0x18, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x65, 0x69, - 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x61, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x61, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x73, 0x61, 0x74, - 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x17, 0x0a, 0x07, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x66, 0x65, 0x65, 0x53, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, - 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, - 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x42, 0x0a, 0x0e, - 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x22, 0x59, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x0f, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x1a, 0x02, - 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, - 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x02, - 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, - 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, - 0x05, 0x22, 0xd5, 0x02, 0x0a, 0x0b, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x49, 0x64, - 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, - 0x65, 0x6d, 0x70, 0x74, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x61, - 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x4e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x12, 0x28, 0x0a, 0x07, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, - 0x65, 0x22, 0x36, 0x0a, 0x0a, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, - 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, - 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0xb4, 0x02, 0x0a, 0x13, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, - 0x65, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, - 0x22, 0xca, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x10, 0x66, 0x69, 0x72, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, - 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, - 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x65, 0x0a, - 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, - 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x30, 0x0a, 0x14, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x12, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x4f, - 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x68, 0x74, - 0x6c, 0x63, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, - 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, - 0x21, 0x0a, 0x0c, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 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, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x41, 0x62, 0x61, - 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x38, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, + 0x01, 0x22, 0x59, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x12, 0x0a, 0x10, + 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x95, 0x02, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, + 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, + 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, + 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, + 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x61, + 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x69, 0x6e, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, + 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x22, 0xb5, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, + 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x61, + 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x09, 0x64, 0x61, 0x79, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x77, 0x65, + 0x65, 0x6b, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0a, 0x77, 0x65, 0x65, 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x22, 0x0a, 0x0d, + 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, + 0x22, 0x52, 0x0a, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x12, 0x22, + 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, + 0x70, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, + 0x65, 0x50, 0x70, 0x6d, 0x22, 0xaa, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, + 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, + 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, + 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x66, + 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, + 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, + 0x78, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x35, 0x0a, + 0x17, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x5f, 0x73, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, + 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, + 0x66, 0x65, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, 0x0a, 0x69, 0x6e, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, + 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, + 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x52, 0x0a, 0x14, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x24, + 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0f, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x22, 0x85, 0x03, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x20, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, + 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x49, 0x6e, 0x12, 0x22, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, + 0x01, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, + 0x61, 0x6d, 0x74, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, + 0x74, 0x49, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x66, 0x65, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x07, 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, + 0x5f, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, + 0x61, 0x6d, 0x74, 0x49, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, + 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0a, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x22, + 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x49, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, + 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4f, 0x75, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x19, 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, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 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, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0c, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x73, 0x68, 0x69, 0x6d, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, - 0x68, 0x69, 0x6d, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x31, 0x0a, 0x16, 0x69, 0x5f, 0x6b, 0x6e, 0x6f, - 0x77, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x5f, 0x61, 0x6d, 0x5f, 0x64, 0x6f, 0x69, 0x6e, - 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, - 0x61, 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x18, 0x0a, 0x16, 0x41, 0x62, - 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x11, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, - 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x1d, 0x0a, - 0x0a, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x53, 0x70, 0x65, 0x63, 0x22, 0x35, 0x0a, 0x12, - 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0xf0, 0x04, 0x0a, - 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, - 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x73, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, - 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x61, - 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, - 0x61, 0x63, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, - 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x6c, - 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x52, - 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x48, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, - 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x50, 0x61, 0x74, 0x68, 0x52, 0x0c, 0x62, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50, 0x61, 0x74, - 0x68, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x59, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x69, 0x73, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x95, - 0x02, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x61, - 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x65, 0x65, - 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, - 0x66, 0x65, 0x65, 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, - 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x66, 0x65, 0x65, - 0x52, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x12, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x61, 0x73, 0x65, - 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x69, 0x6e, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6d, 0x69, 0x6c, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, - 0x50, 0x65, 0x72, 0x4d, 0x69, 0x6c, 0x22, 0xb5, 0x01, 0x0a, 0x11, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x64, 0x61, 0x79, 0x5f, - 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, - 0x61, 0x79, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x77, 0x65, 0x65, 0x6b, - 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, - 0x77, 0x65, 0x65, 0x6b, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x6f, - 0x6e, 0x74, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x46, 0x65, 0x65, 0x53, 0x75, 0x6d, 0x22, 0x52, - 0x0a, 0x0a, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x12, 0x22, 0x0a, 0x0d, - 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, - 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, - 0x70, 0x6d, 0x22, 0xaa, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, - 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, - 0x6c, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, - 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, - 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, - 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x6d, - 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x5f, 0x73, 0x70, 0x65, - 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6d, 0x69, - 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x65, 0x64, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x22, - 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x50, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2c, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x52, - 0x0a, 0x14, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0e, - 0x6e, 0x75, 0x6d, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4d, 0x61, 0x78, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, - 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, - 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x22, 0x85, - 0x03, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x20, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x5f, - 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x08, 0x63, 0x68, - 0x61, 0x6e, 0x49, 0x64, 0x49, 0x6e, 0x12, 0x22, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, - 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, - 0x09, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x6d, - 0x74, 0x5f, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x6d, 0x74, 0x49, - 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, - 0x66, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x61, 0x6d, 0x74, 0x5f, 0x69, - 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x6d, - 0x74, 0x49, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x6d, 0x74, 0x5f, 0x6f, - 0x75, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, - 0x6d, 0x74, 0x4f, 0x75, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x22, 0x0a, 0x0d, - 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x49, 0x6e, - 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6f, - 0x75, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x41, 0x6c, - 0x69, 0x61, 0x73, 0x4f, 0x75, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x19, 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, 0x43, 0x0a, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, - 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x50, 0x0a, 0x1a, 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, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, - 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x73, 0x0a, - 0x0f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9f, 0x01, - 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, 0x11, 0x6d, - 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, - 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, - 0x49, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0b, 0x63, - 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x18, 0x52, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, + 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x64, 0x0a, 0x0d, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, + 0x73, 0x0a, 0x0f, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x68, + 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x9f, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x13, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x67, + 0x6c, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x42, 0x0a, + 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x17, 0x0a, 0x15, 0x52, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 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, 0x22, 0x1a, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x0a, - 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, - 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, - 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, - 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, - 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, - 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0b, - 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18, - 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, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x70, 0x22, 0x49, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x12, 0x37, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, + 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x22, 0x8e, 0x01, 0x0a, + 0x18, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x48, 0x00, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x42, 0x08, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x17, 0x0a, + 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 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, 0x22, 0x1a, 0x0a, 0x18, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, + 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, + 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x32, 0x0a, 0x14, 0x42, 0x61, 0x6b, 0x65, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0x18, 0x0a, 0x16, + 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3b, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, + 0x49, 0x64, 0x73, 0x22, 0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, + 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x34, + 0x0a, 0x18, 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, 0x18, 0x0a, 0x07, 0x64, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x22, 0x55, 0x0a, 0x16, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3b, + 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, + 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a, + 0x07, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, + 0x32, 0x35, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, + 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, + 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, + 0x14, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, + 0x43, 0x54, 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, + 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, + 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, + 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, + 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, + 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, + 0x0a, 0x1b, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, + 0x54, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, + 0x19, 0x0a, 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, + 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, + 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, + 0x0f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, + 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, + 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, + 0x12, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, + 0x4d, 0x41, 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, + 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, + 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, + 0x49, 0x4d, 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, + 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, + 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, + 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, + 0x45, 0x4c, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, + 0x19, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, + 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, + 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, + 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, + 0x24, 0x0a, 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, + 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, + 0x49, 0x4e, 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x5f, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, + 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, + 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, + 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, + 0x4f, 0x5f, 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, + 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, + 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, + 0x44, 0x10, 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, + 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12, + 0x15, 0x0a, 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, + 0x55, 0x4e, 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, + 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, + 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, + 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, + 0x6d, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, + 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x22, 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x09, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, + 0x22, 0x36, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x4c, 0x69, 0x73, - 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0xe4, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x64, 0x0a, 0x12, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 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, 0x2e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x11, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x63, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcc, 0x08, 0x0a, 0x07, 0x46, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, - 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x3b, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, - 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x61, 0x5f, 0x32, 0x35, - 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x53, 0x68, - 0x61, 0x32, 0x35, 0x36, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, - 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x8b, 0x06, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x28, 0x0a, 0x24, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, - 0x5f, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4d, - 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x1c, 0x0a, - 0x18, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, - 0x4e, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x46, - 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, - 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, - 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x04, 0x12, 0x19, 0x0a, - 0x15, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, - 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x41, - 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x45, - 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x07, - 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, - 0x4e, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x49, - 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x4d, 0x41, - 0x43, 0x10, 0x09, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, - 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x0a, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4d, - 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x42, 0x45, 0x4c, 0x4f, 0x57, 0x5f, 0x4d, 0x49, 0x4e, 0x49, 0x4d, - 0x55, 0x4d, 0x10, 0x0b, 0x12, 0x14, 0x0a, 0x10, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, - 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, - 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x43, 0x4c, 0x54, 0x56, 0x5f, 0x45, 0x58, 0x50, - 0x49, 0x52, 0x59, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, - 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x1d, 0x0a, 0x19, 0x54, - 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, - 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x0f, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, - 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x45, 0x41, 0x54, - 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x10, 0x12, 0x24, 0x0a, - 0x20, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, - 0x4c, 0x5f, 0x46, 0x45, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, - 0x47, 0x10, 0x11, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x4e, - 0x45, 0x58, 0x54, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, - 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, - 0x4c, 0x55, 0x52, 0x45, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, - 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x45, 0x52, 0x4d, 0x41, 0x4e, 0x45, 0x4e, 0x54, 0x5f, - 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, - 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x45, 0x58, 0x50, 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, - 0x46, 0x41, 0x52, 0x10, 0x16, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x50, 0x50, 0x5f, 0x54, 0x49, 0x4d, - 0x45, 0x4f, 0x55, 0x54, 0x10, 0x17, 0x12, 0x19, 0x0a, 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, - 0x44, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x10, - 0x18, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4f, 0x4e, 0x49, - 0x4f, 0x4e, 0x5f, 0x42, 0x4c, 0x49, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x19, 0x12, 0x15, 0x0a, - 0x10, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x10, 0xe5, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, - 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0xe6, 0x07, 0x12, 0x17, 0x0a, 0x12, 0x55, 0x4e, - 0x52, 0x45, 0x41, 0x44, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x10, 0xe7, 0x07, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xb3, 0x03, 0x0a, 0x0d, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x06, 0x63, - 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x66, - 0x6c, 0x61, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x26, 0x0a, - 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, - 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x69, - 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x4d, 0x73, 0x61, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, - 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x74, 0x6c, 0x63, 0x5f, - 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0f, 0x68, 0x74, 0x6c, 0x63, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4d, - 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x6f, 0x70, 0x61, - 0x71, 0x75, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, - 0x65, 0x78, 0x74, 0x72, 0x61, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, - 0x5d, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, - 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, - 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x52, 0x03, 0x6f, 0x70, 0x73, 0x22, 0x36, - 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, - 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xf4, 0x02, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, 0x74, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, - 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x2d, - 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, - 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, - 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x34, 0x0a, 0x0a, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, - 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, - 0x72, 0x69, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, - 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, 0x70, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, - 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x72, 0x65, - 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, - 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, - 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, - 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x14, 0x0a, - 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, - 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, - 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, - 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61, 0x76, 0x65, - 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, - 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b, 0x01, 0x0a, - 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, - 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, 0x02, 0x0a, 0x10, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, - 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, - 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x43, 0x52, 0x49, - 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, - 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, - 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, - 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, 0x43, 0x52, 0x49, - 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x43, 0x52, - 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, - 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x53, - 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, 0x4c, 0x4c, 0x44, - 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44, 0x41, 0x52, - 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x31, 0x5f, 0x54, - 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, 0x43, 0x6f, 0x69, 0x6e, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x55, 0x53, - 0x45, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, - 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x4c, 0x41, - 0x52, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x52, 0x41, 0x54, - 0x45, 0x47, 0x59, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x2a, 0xac, 0x01, 0x0a, - 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, - 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, - 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, - 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x1e, 0x0a, - 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, - 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, 0x1d, 0x0a, - 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, 0x5f, 0x50, - 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 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, 0x0a, 0x0e, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, - 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, - 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, - 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x49, - 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x0b, - 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x53, - 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, 0x5f, 0x4c, - 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 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, 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, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x75, 0x6c, + 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, + 0x75, 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x2c, 0x0a, 0x14, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0xf4, 0x02, 0x0a, 0x14, 0x52, 0x50, 0x43, 0x4d, + 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x72, 0x61, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x61, 0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x61, 0x76, + 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x61, 0x76, 0x65, 0x61, + 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, + 0x74, 0x68, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, + 0x12, 0x2d, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x23, 0x0a, 0x0c, 0x72, 0x65, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x65, 0x67, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x42, 0x10, 0x0a, 0x0e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x34, + 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x41, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x0f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, + 0x6c, 0x55, 0x72, 0x69, 0x22, 0xab, 0x01, 0x0a, 0x0a, 0x52, 0x50, 0x43, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x66, 0x75, + 0x6c, 0x6c, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x46, 0x75, 0x6c, 0x6c, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x70, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x22, 0xc0, 0x01, 0x0a, 0x15, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x0a, + 0x72, 0x65, 0x66, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x72, 0x65, 0x66, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x08, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x42, + 0x14, 0x0a, 0x12, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x76, + 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x43, 0x61, + 0x76, 0x65, 0x61, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x64, + 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0c, 0x72, 0x65, 0x61, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x8b, + 0x01, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x46, 0x65, 0x65, 0x64, + 0x62, 0x61, 0x63, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2a, 0xcb, 0x02, 0x0a, + 0x10, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x1b, + 0x0a, 0x17, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x43, + 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x26, 0x0a, 0x22, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, + 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, + 0x48, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x5f, 0x53, 0x43, + 0x52, 0x49, 0x50, 0x54, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x53, + 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, + 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x49, 0x47, 0x10, 0x05, 0x12, 0x18, 0x0a, + 0x14, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x55, 0x4c, + 0x4c, 0x44, 0x41, 0x54, 0x41, 0x10, 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x4e, 0x44, + 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x31, + 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x09, 0x2a, 0x62, 0x0a, 0x15, 0x43, 0x6f, + 0x69, 0x6e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x12, 0x1e, 0x0a, 0x1a, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, + 0x55, 0x53, 0x45, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, + 0x47, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, + 0x4c, 0x41, 0x52, 0x47, 0x45, 0x53, 0x54, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x52, + 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x2a, 0xac, + 0x01, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, + 0x0a, 0x13, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, + 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x4e, 0x45, 0x53, 0x54, 0x45, + 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, + 0x1e, 0x0a, 0x1a, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, + 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x02, 0x12, + 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x4e, 0x45, 0x53, 0x54, 0x45, 0x44, + 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 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, 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, + 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, + 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, + 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, + 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, 0x50, + 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, 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, 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, + 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, 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, + 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, - 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, - 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, - 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, - 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, - 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, - 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, - 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, - 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, - 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 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, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, - 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 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, 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, 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 ( @@ -21282,7 +21518,7 @@ func file_lightning_proto_rawDescGZIP() []byte { } var file_lightning_proto_enumTypes = make([]protoimpl.EnumInfo, 21) -var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 226) +var file_lightning_proto_msgTypes = make([]protoimpl.MessageInfo, 228) var file_lightning_proto_goTypes = []interface{}{ (OutputScriptType)(0), // 0: lnrpc.OutputScriptType (CoinSelectionStrategy)(0), // 1: lnrpc.CoinSelectionStrategy @@ -21371,166 +21607,168 @@ var file_lightning_proto_goTypes = []interface{}{ (*Chain)(nil), // 84: lnrpc.Chain (*ConfirmationUpdate)(nil), // 85: lnrpc.ConfirmationUpdate (*ChannelOpenUpdate)(nil), // 86: lnrpc.ChannelOpenUpdate - (*ChannelCloseUpdate)(nil), // 87: lnrpc.ChannelCloseUpdate - (*CloseChannelRequest)(nil), // 88: lnrpc.CloseChannelRequest - (*CloseStatusUpdate)(nil), // 89: lnrpc.CloseStatusUpdate - (*PendingUpdate)(nil), // 90: lnrpc.PendingUpdate - (*InstantUpdate)(nil), // 91: lnrpc.InstantUpdate - (*ReadyForPsbtFunding)(nil), // 92: lnrpc.ReadyForPsbtFunding - (*BatchOpenChannelRequest)(nil), // 93: lnrpc.BatchOpenChannelRequest - (*BatchOpenChannel)(nil), // 94: lnrpc.BatchOpenChannel - (*BatchOpenChannelResponse)(nil), // 95: lnrpc.BatchOpenChannelResponse - (*OpenChannelRequest)(nil), // 96: lnrpc.OpenChannelRequest - (*OpenStatusUpdate)(nil), // 97: lnrpc.OpenStatusUpdate - (*KeyLocator)(nil), // 98: lnrpc.KeyLocator - (*KeyDescriptor)(nil), // 99: lnrpc.KeyDescriptor - (*ChanPointShim)(nil), // 100: lnrpc.ChanPointShim - (*PsbtShim)(nil), // 101: lnrpc.PsbtShim - (*FundingShim)(nil), // 102: lnrpc.FundingShim - (*FundingShimCancel)(nil), // 103: lnrpc.FundingShimCancel - (*FundingPsbtVerify)(nil), // 104: lnrpc.FundingPsbtVerify - (*FundingPsbtFinalize)(nil), // 105: lnrpc.FundingPsbtFinalize - (*FundingTransitionMsg)(nil), // 106: lnrpc.FundingTransitionMsg - (*FundingStateStepResp)(nil), // 107: lnrpc.FundingStateStepResp - (*PendingHTLC)(nil), // 108: lnrpc.PendingHTLC - (*PendingChannelsRequest)(nil), // 109: lnrpc.PendingChannelsRequest - (*PendingChannelsResponse)(nil), // 110: lnrpc.PendingChannelsResponse - (*ChannelEventSubscription)(nil), // 111: lnrpc.ChannelEventSubscription - (*ChannelEventUpdate)(nil), // 112: lnrpc.ChannelEventUpdate - (*WalletAccountBalance)(nil), // 113: lnrpc.WalletAccountBalance - (*WalletBalanceRequest)(nil), // 114: lnrpc.WalletBalanceRequest - (*WalletBalanceResponse)(nil), // 115: lnrpc.WalletBalanceResponse - (*Amount)(nil), // 116: lnrpc.Amount - (*ChannelBalanceRequest)(nil), // 117: lnrpc.ChannelBalanceRequest - (*ChannelBalanceResponse)(nil), // 118: lnrpc.ChannelBalanceResponse - (*QueryRoutesRequest)(nil), // 119: lnrpc.QueryRoutesRequest - (*NodePair)(nil), // 120: lnrpc.NodePair - (*EdgeLocator)(nil), // 121: lnrpc.EdgeLocator - (*QueryRoutesResponse)(nil), // 122: lnrpc.QueryRoutesResponse - (*Hop)(nil), // 123: lnrpc.Hop - (*MPPRecord)(nil), // 124: lnrpc.MPPRecord - (*AMPRecord)(nil), // 125: lnrpc.AMPRecord - (*Route)(nil), // 126: lnrpc.Route - (*NodeInfoRequest)(nil), // 127: lnrpc.NodeInfoRequest - (*NodeInfo)(nil), // 128: lnrpc.NodeInfo - (*LightningNode)(nil), // 129: lnrpc.LightningNode - (*NodeAddress)(nil), // 130: lnrpc.NodeAddress - (*RoutingPolicy)(nil), // 131: lnrpc.RoutingPolicy - (*ChannelEdge)(nil), // 132: lnrpc.ChannelEdge - (*ChannelGraphRequest)(nil), // 133: lnrpc.ChannelGraphRequest - (*ChannelGraph)(nil), // 134: lnrpc.ChannelGraph - (*NodeMetricsRequest)(nil), // 135: lnrpc.NodeMetricsRequest - (*NodeMetricsResponse)(nil), // 136: lnrpc.NodeMetricsResponse - (*FloatMetric)(nil), // 137: lnrpc.FloatMetric - (*ChanInfoRequest)(nil), // 138: lnrpc.ChanInfoRequest - (*NetworkInfoRequest)(nil), // 139: lnrpc.NetworkInfoRequest - (*NetworkInfo)(nil), // 140: lnrpc.NetworkInfo - (*StopRequest)(nil), // 141: lnrpc.StopRequest - (*StopResponse)(nil), // 142: lnrpc.StopResponse - (*GraphTopologySubscription)(nil), // 143: lnrpc.GraphTopologySubscription - (*GraphTopologyUpdate)(nil), // 144: lnrpc.GraphTopologyUpdate - (*NodeUpdate)(nil), // 145: lnrpc.NodeUpdate - (*ChannelEdgeUpdate)(nil), // 146: lnrpc.ChannelEdgeUpdate - (*ClosedChannelUpdate)(nil), // 147: lnrpc.ClosedChannelUpdate - (*HopHint)(nil), // 148: lnrpc.HopHint - (*SetID)(nil), // 149: lnrpc.SetID - (*RouteHint)(nil), // 150: lnrpc.RouteHint - (*BlindedPaymentPath)(nil), // 151: lnrpc.BlindedPaymentPath - (*BlindedPath)(nil), // 152: lnrpc.BlindedPath - (*BlindedHop)(nil), // 153: lnrpc.BlindedHop - (*AMPInvoiceState)(nil), // 154: lnrpc.AMPInvoiceState - (*Invoice)(nil), // 155: lnrpc.Invoice - (*BlindedPathConfig)(nil), // 156: lnrpc.BlindedPathConfig - (*InvoiceHTLC)(nil), // 157: lnrpc.InvoiceHTLC - (*AMP)(nil), // 158: lnrpc.AMP - (*AddInvoiceResponse)(nil), // 159: lnrpc.AddInvoiceResponse - (*PaymentHash)(nil), // 160: lnrpc.PaymentHash - (*ListInvoiceRequest)(nil), // 161: lnrpc.ListInvoiceRequest - (*ListInvoiceResponse)(nil), // 162: lnrpc.ListInvoiceResponse - (*InvoiceSubscription)(nil), // 163: lnrpc.InvoiceSubscription - (*Payment)(nil), // 164: lnrpc.Payment - (*HTLCAttempt)(nil), // 165: lnrpc.HTLCAttempt - (*ListPaymentsRequest)(nil), // 166: lnrpc.ListPaymentsRequest - (*ListPaymentsResponse)(nil), // 167: lnrpc.ListPaymentsResponse - (*DeletePaymentRequest)(nil), // 168: lnrpc.DeletePaymentRequest - (*DeleteAllPaymentsRequest)(nil), // 169: lnrpc.DeleteAllPaymentsRequest - (*DeletePaymentResponse)(nil), // 170: lnrpc.DeletePaymentResponse - (*DeleteAllPaymentsResponse)(nil), // 171: lnrpc.DeleteAllPaymentsResponse - (*AbandonChannelRequest)(nil), // 172: lnrpc.AbandonChannelRequest - (*AbandonChannelResponse)(nil), // 173: lnrpc.AbandonChannelResponse - (*DebugLevelRequest)(nil), // 174: lnrpc.DebugLevelRequest - (*DebugLevelResponse)(nil), // 175: lnrpc.DebugLevelResponse - (*PayReqString)(nil), // 176: lnrpc.PayReqString - (*PayReq)(nil), // 177: lnrpc.PayReq - (*Feature)(nil), // 178: lnrpc.Feature - (*FeeReportRequest)(nil), // 179: lnrpc.FeeReportRequest - (*ChannelFeeReport)(nil), // 180: lnrpc.ChannelFeeReport - (*FeeReportResponse)(nil), // 181: lnrpc.FeeReportResponse - (*InboundFee)(nil), // 182: lnrpc.InboundFee - (*PolicyUpdateRequest)(nil), // 183: lnrpc.PolicyUpdateRequest - (*FailedUpdate)(nil), // 184: lnrpc.FailedUpdate - (*PolicyUpdateResponse)(nil), // 185: lnrpc.PolicyUpdateResponse - (*ForwardingHistoryRequest)(nil), // 186: lnrpc.ForwardingHistoryRequest - (*ForwardingEvent)(nil), // 187: lnrpc.ForwardingEvent - (*ForwardingHistoryResponse)(nil), // 188: lnrpc.ForwardingHistoryResponse - (*ExportChannelBackupRequest)(nil), // 189: lnrpc.ExportChannelBackupRequest - (*ChannelBackup)(nil), // 190: lnrpc.ChannelBackup - (*MultiChanBackup)(nil), // 191: lnrpc.MultiChanBackup - (*ChanBackupExportRequest)(nil), // 192: lnrpc.ChanBackupExportRequest - (*ChanBackupSnapshot)(nil), // 193: lnrpc.ChanBackupSnapshot - (*ChannelBackups)(nil), // 194: lnrpc.ChannelBackups - (*RestoreChanBackupRequest)(nil), // 195: lnrpc.RestoreChanBackupRequest - (*RestoreBackupResponse)(nil), // 196: lnrpc.RestoreBackupResponse - (*ChannelBackupSubscription)(nil), // 197: lnrpc.ChannelBackupSubscription - (*VerifyChanBackupResponse)(nil), // 198: lnrpc.VerifyChanBackupResponse - (*MacaroonPermission)(nil), // 199: lnrpc.MacaroonPermission - (*BakeMacaroonRequest)(nil), // 200: lnrpc.BakeMacaroonRequest - (*BakeMacaroonResponse)(nil), // 201: lnrpc.BakeMacaroonResponse - (*ListMacaroonIDsRequest)(nil), // 202: lnrpc.ListMacaroonIDsRequest - (*ListMacaroonIDsResponse)(nil), // 203: lnrpc.ListMacaroonIDsResponse - (*DeleteMacaroonIDRequest)(nil), // 204: lnrpc.DeleteMacaroonIDRequest - (*DeleteMacaroonIDResponse)(nil), // 205: lnrpc.DeleteMacaroonIDResponse - (*MacaroonPermissionList)(nil), // 206: lnrpc.MacaroonPermissionList - (*ListPermissionsRequest)(nil), // 207: lnrpc.ListPermissionsRequest - (*ListPermissionsResponse)(nil), // 208: lnrpc.ListPermissionsResponse - (*Failure)(nil), // 209: lnrpc.Failure - (*ChannelUpdate)(nil), // 210: lnrpc.ChannelUpdate - (*MacaroonId)(nil), // 211: lnrpc.MacaroonId - (*Op)(nil), // 212: lnrpc.Op - (*CheckMacPermRequest)(nil), // 213: lnrpc.CheckMacPermRequest - (*CheckMacPermResponse)(nil), // 214: lnrpc.CheckMacPermResponse - (*RPCMiddlewareRequest)(nil), // 215: lnrpc.RPCMiddlewareRequest - (*StreamAuth)(nil), // 216: lnrpc.StreamAuth - (*RPCMessage)(nil), // 217: lnrpc.RPCMessage - (*RPCMiddlewareResponse)(nil), // 218: lnrpc.RPCMiddlewareResponse - (*MiddlewareRegistration)(nil), // 219: lnrpc.MiddlewareRegistration - (*InterceptFeedback)(nil), // 220: lnrpc.InterceptFeedback - nil, // 221: lnrpc.SendRequest.DestCustomRecordsEntry - nil, // 222: lnrpc.EstimateFeeRequest.AddrToAmountEntry - nil, // 223: lnrpc.SendManyRequest.AddrToAmountEntry - nil, // 224: lnrpc.Peer.FeaturesEntry - nil, // 225: lnrpc.GetInfoResponse.FeaturesEntry - nil, // 226: lnrpc.GetDebugInfoResponse.ConfigEntry - (*PendingChannelsResponse_PendingChannel)(nil), // 227: lnrpc.PendingChannelsResponse.PendingChannel - (*PendingChannelsResponse_PendingOpenChannel)(nil), // 228: lnrpc.PendingChannelsResponse.PendingOpenChannel - (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 229: lnrpc.PendingChannelsResponse.WaitingCloseChannel - (*PendingChannelsResponse_Commitments)(nil), // 230: lnrpc.PendingChannelsResponse.Commitments - (*PendingChannelsResponse_ClosedChannel)(nil), // 231: lnrpc.PendingChannelsResponse.ClosedChannel - (*PendingChannelsResponse_ForceClosedChannel)(nil), // 232: lnrpc.PendingChannelsResponse.ForceClosedChannel - nil, // 233: lnrpc.WalletBalanceResponse.AccountBalanceEntry - nil, // 234: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - nil, // 235: lnrpc.Hop.CustomRecordsEntry - nil, // 236: lnrpc.LightningNode.FeaturesEntry - nil, // 237: lnrpc.LightningNode.CustomRecordsEntry - nil, // 238: lnrpc.RoutingPolicy.CustomRecordsEntry - nil, // 239: lnrpc.ChannelEdge.CustomRecordsEntry - nil, // 240: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - nil, // 241: lnrpc.NodeUpdate.FeaturesEntry - nil, // 242: lnrpc.Invoice.FeaturesEntry - nil, // 243: lnrpc.Invoice.AmpInvoiceStateEntry - nil, // 244: lnrpc.InvoiceHTLC.CustomRecordsEntry - nil, // 245: lnrpc.PayReq.FeaturesEntry - nil, // 246: lnrpc.ListPermissionsResponse.MethodPermissionsEntry + (*CloseOutput)(nil), // 87: lnrpc.CloseOutput + (*ChannelCloseUpdate)(nil), // 88: lnrpc.ChannelCloseUpdate + (*CloseChannelRequest)(nil), // 89: lnrpc.CloseChannelRequest + (*CloseStatusUpdate)(nil), // 90: lnrpc.CloseStatusUpdate + (*PendingUpdate)(nil), // 91: lnrpc.PendingUpdate + (*InstantUpdate)(nil), // 92: lnrpc.InstantUpdate + (*ReadyForPsbtFunding)(nil), // 93: lnrpc.ReadyForPsbtFunding + (*BatchOpenChannelRequest)(nil), // 94: lnrpc.BatchOpenChannelRequest + (*BatchOpenChannel)(nil), // 95: lnrpc.BatchOpenChannel + (*BatchOpenChannelResponse)(nil), // 96: lnrpc.BatchOpenChannelResponse + (*OpenChannelRequest)(nil), // 97: lnrpc.OpenChannelRequest + (*OpenStatusUpdate)(nil), // 98: lnrpc.OpenStatusUpdate + (*KeyLocator)(nil), // 99: lnrpc.KeyLocator + (*KeyDescriptor)(nil), // 100: lnrpc.KeyDescriptor + (*ChanPointShim)(nil), // 101: lnrpc.ChanPointShim + (*PsbtShim)(nil), // 102: lnrpc.PsbtShim + (*FundingShim)(nil), // 103: lnrpc.FundingShim + (*FundingShimCancel)(nil), // 104: lnrpc.FundingShimCancel + (*FundingPsbtVerify)(nil), // 105: lnrpc.FundingPsbtVerify + (*FundingPsbtFinalize)(nil), // 106: lnrpc.FundingPsbtFinalize + (*FundingTransitionMsg)(nil), // 107: lnrpc.FundingTransitionMsg + (*FundingStateStepResp)(nil), // 108: lnrpc.FundingStateStepResp + (*PendingHTLC)(nil), // 109: lnrpc.PendingHTLC + (*PendingChannelsRequest)(nil), // 110: lnrpc.PendingChannelsRequest + (*PendingChannelsResponse)(nil), // 111: lnrpc.PendingChannelsResponse + (*ChannelEventSubscription)(nil), // 112: lnrpc.ChannelEventSubscription + (*ChannelEventUpdate)(nil), // 113: lnrpc.ChannelEventUpdate + (*WalletAccountBalance)(nil), // 114: lnrpc.WalletAccountBalance + (*WalletBalanceRequest)(nil), // 115: lnrpc.WalletBalanceRequest + (*WalletBalanceResponse)(nil), // 116: lnrpc.WalletBalanceResponse + (*Amount)(nil), // 117: lnrpc.Amount + (*ChannelBalanceRequest)(nil), // 118: lnrpc.ChannelBalanceRequest + (*ChannelBalanceResponse)(nil), // 119: lnrpc.ChannelBalanceResponse + (*QueryRoutesRequest)(nil), // 120: lnrpc.QueryRoutesRequest + (*NodePair)(nil), // 121: lnrpc.NodePair + (*EdgeLocator)(nil), // 122: lnrpc.EdgeLocator + (*QueryRoutesResponse)(nil), // 123: lnrpc.QueryRoutesResponse + (*Hop)(nil), // 124: lnrpc.Hop + (*MPPRecord)(nil), // 125: lnrpc.MPPRecord + (*AMPRecord)(nil), // 126: lnrpc.AMPRecord + (*Route)(nil), // 127: lnrpc.Route + (*NodeInfoRequest)(nil), // 128: lnrpc.NodeInfoRequest + (*NodeInfo)(nil), // 129: lnrpc.NodeInfo + (*LightningNode)(nil), // 130: lnrpc.LightningNode + (*NodeAddress)(nil), // 131: lnrpc.NodeAddress + (*RoutingPolicy)(nil), // 132: lnrpc.RoutingPolicy + (*ChannelEdge)(nil), // 133: lnrpc.ChannelEdge + (*ChannelGraphRequest)(nil), // 134: lnrpc.ChannelGraphRequest + (*ChannelGraph)(nil), // 135: lnrpc.ChannelGraph + (*NodeMetricsRequest)(nil), // 136: lnrpc.NodeMetricsRequest + (*NodeMetricsResponse)(nil), // 137: lnrpc.NodeMetricsResponse + (*FloatMetric)(nil), // 138: lnrpc.FloatMetric + (*ChanInfoRequest)(nil), // 139: lnrpc.ChanInfoRequest + (*NetworkInfoRequest)(nil), // 140: lnrpc.NetworkInfoRequest + (*NetworkInfo)(nil), // 141: lnrpc.NetworkInfo + (*StopRequest)(nil), // 142: lnrpc.StopRequest + (*StopResponse)(nil), // 143: lnrpc.StopResponse + (*GraphTopologySubscription)(nil), // 144: lnrpc.GraphTopologySubscription + (*GraphTopologyUpdate)(nil), // 145: lnrpc.GraphTopologyUpdate + (*NodeUpdate)(nil), // 146: lnrpc.NodeUpdate + (*ChannelEdgeUpdate)(nil), // 147: lnrpc.ChannelEdgeUpdate + (*ClosedChannelUpdate)(nil), // 148: lnrpc.ClosedChannelUpdate + (*HopHint)(nil), // 149: lnrpc.HopHint + (*SetID)(nil), // 150: lnrpc.SetID + (*RouteHint)(nil), // 151: lnrpc.RouteHint + (*BlindedPaymentPath)(nil), // 152: lnrpc.BlindedPaymentPath + (*BlindedPath)(nil), // 153: lnrpc.BlindedPath + (*BlindedHop)(nil), // 154: lnrpc.BlindedHop + (*AMPInvoiceState)(nil), // 155: lnrpc.AMPInvoiceState + (*Invoice)(nil), // 156: lnrpc.Invoice + (*BlindedPathConfig)(nil), // 157: lnrpc.BlindedPathConfig + (*InvoiceHTLC)(nil), // 158: lnrpc.InvoiceHTLC + (*AMP)(nil), // 159: lnrpc.AMP + (*AddInvoiceResponse)(nil), // 160: lnrpc.AddInvoiceResponse + (*PaymentHash)(nil), // 161: lnrpc.PaymentHash + (*ListInvoiceRequest)(nil), // 162: lnrpc.ListInvoiceRequest + (*ListInvoiceResponse)(nil), // 163: lnrpc.ListInvoiceResponse + (*InvoiceSubscription)(nil), // 164: lnrpc.InvoiceSubscription + (*Payment)(nil), // 165: lnrpc.Payment + (*HTLCAttempt)(nil), // 166: lnrpc.HTLCAttempt + (*ListPaymentsRequest)(nil), // 167: lnrpc.ListPaymentsRequest + (*ListPaymentsResponse)(nil), // 168: lnrpc.ListPaymentsResponse + (*DeletePaymentRequest)(nil), // 169: lnrpc.DeletePaymentRequest + (*DeleteAllPaymentsRequest)(nil), // 170: lnrpc.DeleteAllPaymentsRequest + (*DeletePaymentResponse)(nil), // 171: lnrpc.DeletePaymentResponse + (*DeleteAllPaymentsResponse)(nil), // 172: lnrpc.DeleteAllPaymentsResponse + (*AbandonChannelRequest)(nil), // 173: lnrpc.AbandonChannelRequest + (*AbandonChannelResponse)(nil), // 174: lnrpc.AbandonChannelResponse + (*DebugLevelRequest)(nil), // 175: lnrpc.DebugLevelRequest + (*DebugLevelResponse)(nil), // 176: lnrpc.DebugLevelResponse + (*PayReqString)(nil), // 177: lnrpc.PayReqString + (*PayReq)(nil), // 178: lnrpc.PayReq + (*Feature)(nil), // 179: lnrpc.Feature + (*FeeReportRequest)(nil), // 180: lnrpc.FeeReportRequest + (*ChannelFeeReport)(nil), // 181: lnrpc.ChannelFeeReport + (*FeeReportResponse)(nil), // 182: lnrpc.FeeReportResponse + (*InboundFee)(nil), // 183: lnrpc.InboundFee + (*PolicyUpdateRequest)(nil), // 184: lnrpc.PolicyUpdateRequest + (*FailedUpdate)(nil), // 185: lnrpc.FailedUpdate + (*PolicyUpdateResponse)(nil), // 186: lnrpc.PolicyUpdateResponse + (*ForwardingHistoryRequest)(nil), // 187: lnrpc.ForwardingHistoryRequest + (*ForwardingEvent)(nil), // 188: lnrpc.ForwardingEvent + (*ForwardingHistoryResponse)(nil), // 189: lnrpc.ForwardingHistoryResponse + (*ExportChannelBackupRequest)(nil), // 190: lnrpc.ExportChannelBackupRequest + (*ChannelBackup)(nil), // 191: lnrpc.ChannelBackup + (*MultiChanBackup)(nil), // 192: lnrpc.MultiChanBackup + (*ChanBackupExportRequest)(nil), // 193: lnrpc.ChanBackupExportRequest + (*ChanBackupSnapshot)(nil), // 194: lnrpc.ChanBackupSnapshot + (*ChannelBackups)(nil), // 195: lnrpc.ChannelBackups + (*RestoreChanBackupRequest)(nil), // 196: lnrpc.RestoreChanBackupRequest + (*RestoreBackupResponse)(nil), // 197: lnrpc.RestoreBackupResponse + (*ChannelBackupSubscription)(nil), // 198: lnrpc.ChannelBackupSubscription + (*VerifyChanBackupResponse)(nil), // 199: lnrpc.VerifyChanBackupResponse + (*MacaroonPermission)(nil), // 200: lnrpc.MacaroonPermission + (*BakeMacaroonRequest)(nil), // 201: lnrpc.BakeMacaroonRequest + (*BakeMacaroonResponse)(nil), // 202: lnrpc.BakeMacaroonResponse + (*ListMacaroonIDsRequest)(nil), // 203: lnrpc.ListMacaroonIDsRequest + (*ListMacaroonIDsResponse)(nil), // 204: lnrpc.ListMacaroonIDsResponse + (*DeleteMacaroonIDRequest)(nil), // 205: lnrpc.DeleteMacaroonIDRequest + (*DeleteMacaroonIDResponse)(nil), // 206: lnrpc.DeleteMacaroonIDResponse + (*MacaroonPermissionList)(nil), // 207: lnrpc.MacaroonPermissionList + (*ListPermissionsRequest)(nil), // 208: lnrpc.ListPermissionsRequest + (*ListPermissionsResponse)(nil), // 209: lnrpc.ListPermissionsResponse + (*Failure)(nil), // 210: lnrpc.Failure + (*ChannelUpdate)(nil), // 211: lnrpc.ChannelUpdate + (*MacaroonId)(nil), // 212: lnrpc.MacaroonId + (*Op)(nil), // 213: lnrpc.Op + (*CheckMacPermRequest)(nil), // 214: lnrpc.CheckMacPermRequest + (*CheckMacPermResponse)(nil), // 215: lnrpc.CheckMacPermResponse + (*RPCMiddlewareRequest)(nil), // 216: lnrpc.RPCMiddlewareRequest + (*StreamAuth)(nil), // 217: lnrpc.StreamAuth + (*RPCMessage)(nil), // 218: lnrpc.RPCMessage + (*RPCMiddlewareResponse)(nil), // 219: lnrpc.RPCMiddlewareResponse + (*MiddlewareRegistration)(nil), // 220: lnrpc.MiddlewareRegistration + (*InterceptFeedback)(nil), // 221: lnrpc.InterceptFeedback + nil, // 222: lnrpc.SendRequest.DestCustomRecordsEntry + nil, // 223: lnrpc.EstimateFeeRequest.AddrToAmountEntry + nil, // 224: lnrpc.SendManyRequest.AddrToAmountEntry + nil, // 225: lnrpc.Peer.FeaturesEntry + nil, // 226: lnrpc.GetInfoResponse.FeaturesEntry + nil, // 227: lnrpc.GetDebugInfoResponse.ConfigEntry + (*PendingChannelsResponse_PendingChannel)(nil), // 228: lnrpc.PendingChannelsResponse.PendingChannel + (*PendingChannelsResponse_PendingOpenChannel)(nil), // 229: lnrpc.PendingChannelsResponse.PendingOpenChannel + (*PendingChannelsResponse_WaitingCloseChannel)(nil), // 230: lnrpc.PendingChannelsResponse.WaitingCloseChannel + (*PendingChannelsResponse_Commitments)(nil), // 231: lnrpc.PendingChannelsResponse.Commitments + (*PendingChannelsResponse_ClosedChannel)(nil), // 232: lnrpc.PendingChannelsResponse.ClosedChannel + (*PendingChannelsResponse_ForceClosedChannel)(nil), // 233: lnrpc.PendingChannelsResponse.ForceClosedChannel + nil, // 234: lnrpc.WalletBalanceResponse.AccountBalanceEntry + nil, // 235: lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + nil, // 236: lnrpc.Hop.CustomRecordsEntry + nil, // 237: lnrpc.LightningNode.FeaturesEntry + nil, // 238: lnrpc.LightningNode.CustomRecordsEntry + nil, // 239: lnrpc.RoutingPolicy.CustomRecordsEntry + nil, // 240: lnrpc.ChannelEdge.CustomRecordsEntry + nil, // 241: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + nil, // 242: lnrpc.NodeUpdate.FeaturesEntry + nil, // 243: lnrpc.Invoice.FeaturesEntry + nil, // 244: lnrpc.Invoice.AmpInvoiceStateEntry + nil, // 245: lnrpc.InvoiceHTLC.CustomRecordsEntry + nil, // 246: lnrpc.Payment.FirstHopCustomRecordsEntry + nil, // 247: lnrpc.PayReq.FeaturesEntry + nil, // 248: lnrpc.ListPermissionsResponse.MethodPermissionsEntry } var file_lightning_proto_depIdxs = []int32{ 2, // 0: lnrpc.Utxo.address_type:type_name -> lnrpc.AddressType @@ -21540,14 +21778,14 @@ var file_lightning_proto_depIdxs = []int32{ 40, // 4: lnrpc.Transaction.previous_outpoints:type_name -> lnrpc.PreviousOutPoint 29, // 5: lnrpc.TransactionDetails.transactions:type_name -> lnrpc.Transaction 32, // 6: lnrpc.SendRequest.fee_limit:type_name -> lnrpc.FeeLimit - 221, // 7: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry + 222, // 7: lnrpc.SendRequest.dest_custom_records:type_name -> lnrpc.SendRequest.DestCustomRecordsEntry 10, // 8: lnrpc.SendRequest.dest_features:type_name -> lnrpc.FeatureBit - 126, // 9: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route - 126, // 10: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 127, // 9: lnrpc.SendResponse.payment_route:type_name -> lnrpc.Route + 127, // 10: lnrpc.SendToRouteRequest.route:type_name -> lnrpc.Route 3, // 11: lnrpc.ChannelAcceptRequest.commitment_type:type_name -> lnrpc.CommitmentType - 222, // 12: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry + 223, // 12: lnrpc.EstimateFeeRequest.AddrToAmount:type_name -> lnrpc.EstimateFeeRequest.AddrToAmountEntry 1, // 13: lnrpc.EstimateFeeRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 223, // 14: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry + 224, // 14: lnrpc.SendManyRequest.AddrToAmount:type_name -> lnrpc.SendManyRequest.AddrToAmountEntry 1, // 15: lnrpc.SendManyRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy 1, // 16: lnrpc.SendCoinsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy 39, // 17: lnrpc.SendCoinsRequest.outpoints:type_name -> lnrpc.OutPoint @@ -21569,300 +21807,304 @@ var file_lightning_proto_depIdxs = []int32{ 39, // 33: lnrpc.Resolution.outpoint:type_name -> lnrpc.OutPoint 68, // 34: lnrpc.ClosedChannelsResponse.channels:type_name -> lnrpc.ChannelCloseSummary 13, // 35: lnrpc.Peer.sync_type:type_name -> lnrpc.Peer.SyncType - 224, // 36: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry + 225, // 36: lnrpc.Peer.features:type_name -> lnrpc.Peer.FeaturesEntry 73, // 37: lnrpc.Peer.errors:type_name -> lnrpc.TimestampedError 72, // 38: lnrpc.ListPeersResponse.peers:type_name -> lnrpc.Peer 14, // 39: lnrpc.PeerEvent.type:type_name -> lnrpc.PeerEvent.EventType 84, // 40: lnrpc.GetInfoResponse.chains:type_name -> lnrpc.Chain - 225, // 41: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry - 226, // 42: lnrpc.GetDebugInfoResponse.config:type_name -> lnrpc.GetDebugInfoResponse.ConfigEntry + 226, // 41: lnrpc.GetInfoResponse.features:type_name -> lnrpc.GetInfoResponse.FeaturesEntry + 227, // 42: lnrpc.GetDebugInfoResponse.config:type_name -> lnrpc.GetDebugInfoResponse.ConfigEntry 38, // 43: lnrpc.ChannelOpenUpdate.channel_point:type_name -> lnrpc.ChannelPoint - 38, // 44: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 90, // 45: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate - 87, // 46: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate - 91, // 47: lnrpc.CloseStatusUpdate.close_instant:type_name -> lnrpc.InstantUpdate - 94, // 48: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel - 1, // 49: lnrpc.BatchOpenChannelRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 3, // 50: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType - 90, // 51: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate - 102, // 52: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim - 3, // 53: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType - 39, // 54: lnrpc.OpenChannelRequest.outpoints:type_name -> lnrpc.OutPoint - 90, // 55: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate - 86, // 56: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate - 92, // 57: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding - 98, // 58: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator - 38, // 59: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint - 99, // 60: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor - 100, // 61: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim - 101, // 62: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim - 102, // 63: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim - 103, // 64: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel - 104, // 65: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify - 105, // 66: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize - 228, // 67: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel - 231, // 68: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel - 232, // 69: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel - 229, // 70: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel - 62, // 71: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel - 68, // 72: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary - 38, // 73: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint - 38, // 74: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint - 90, // 75: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate - 38, // 76: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint - 16, // 77: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType - 233, // 78: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry - 116, // 79: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount - 116, // 80: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount - 116, // 81: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount - 116, // 82: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount - 116, // 83: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount - 116, // 84: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount - 32, // 85: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit - 121, // 86: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator - 120, // 87: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair - 234, // 88: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry - 150, // 89: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint - 151, // 90: lnrpc.QueryRoutesRequest.blinded_payment_paths:type_name -> lnrpc.BlindedPaymentPath - 10, // 91: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit - 126, // 92: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route - 124, // 93: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord - 125, // 94: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord - 235, // 95: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry - 123, // 96: lnrpc.Route.hops:type_name -> lnrpc.Hop - 129, // 97: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode - 132, // 98: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge - 130, // 99: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress - 236, // 100: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry - 237, // 101: lnrpc.LightningNode.custom_records:type_name -> lnrpc.LightningNode.CustomRecordsEntry - 238, // 102: lnrpc.RoutingPolicy.custom_records:type_name -> lnrpc.RoutingPolicy.CustomRecordsEntry - 131, // 103: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy - 131, // 104: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy - 239, // 105: lnrpc.ChannelEdge.custom_records:type_name -> lnrpc.ChannelEdge.CustomRecordsEntry - 129, // 106: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode - 132, // 107: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge - 7, // 108: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType - 240, // 109: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry - 145, // 110: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate - 146, // 111: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate - 147, // 112: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate - 130, // 113: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress - 241, // 114: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry - 38, // 115: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 131, // 116: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy - 38, // 117: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint - 148, // 118: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint - 152, // 119: lnrpc.BlindedPaymentPath.blinded_path:type_name -> lnrpc.BlindedPath - 10, // 120: lnrpc.BlindedPaymentPath.features:type_name -> lnrpc.FeatureBit - 153, // 121: lnrpc.BlindedPath.blinded_hops:type_name -> lnrpc.BlindedHop - 8, // 122: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState - 150, // 123: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint - 17, // 124: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState - 157, // 125: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC - 242, // 126: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry - 243, // 127: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry - 156, // 128: lnrpc.Invoice.blinded_path_config:type_name -> lnrpc.BlindedPathConfig - 8, // 129: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState - 244, // 130: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry - 158, // 131: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP - 155, // 132: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice - 18, // 133: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus - 165, // 134: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt - 9, // 135: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason - 19, // 136: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus - 126, // 137: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route - 209, // 138: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure - 164, // 139: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment - 38, // 140: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint - 150, // 141: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint - 245, // 142: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry - 151, // 143: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath - 180, // 144: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport - 38, // 145: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint - 182, // 146: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee - 39, // 147: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint - 11, // 148: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure - 184, // 149: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate - 187, // 150: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent - 38, // 151: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint - 38, // 152: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint - 38, // 153: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint - 194, // 154: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups - 191, // 155: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup - 190, // 156: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup - 194, // 157: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups - 199, // 158: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission - 199, // 159: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission - 246, // 160: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry - 20, // 161: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode - 210, // 162: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate - 212, // 163: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op - 199, // 164: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission - 216, // 165: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth - 217, // 166: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage - 217, // 167: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage - 219, // 168: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration - 220, // 169: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback - 178, // 170: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature - 178, // 171: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature - 4, // 172: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator - 3, // 173: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType - 227, // 174: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 227, // 175: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 230, // 176: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments - 227, // 177: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 227, // 178: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel - 108, // 179: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC - 15, // 180: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState - 113, // 181: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance - 178, // 182: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature - 137, // 183: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric - 178, // 184: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature - 178, // 185: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature - 154, // 186: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState - 178, // 187: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature - 206, // 188: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList - 114, // 189: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest - 117, // 190: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest - 30, // 191: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest - 42, // 192: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest - 46, // 193: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest - 48, // 194: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest - 30, // 195: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest - 44, // 196: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest - 50, // 197: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest - 52, // 198: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest - 54, // 199: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest - 56, // 200: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest - 58, // 201: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest - 74, // 202: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest - 76, // 203: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription - 78, // 204: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest - 80, // 205: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest - 82, // 206: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest - 109, // 207: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest - 63, // 208: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest - 111, // 209: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription - 70, // 210: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest - 96, // 211: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest - 96, // 212: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest - 93, // 213: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest - 106, // 214: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg - 37, // 215: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse - 88, // 216: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest - 172, // 217: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest - 33, // 218: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest - 33, // 219: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest - 35, // 220: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest - 35, // 221: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest - 155, // 222: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice - 161, // 223: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest - 160, // 224: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash - 163, // 225: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription - 176, // 226: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString - 166, // 227: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest - 168, // 228: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest - 169, // 229: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest - 133, // 230: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest - 135, // 231: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest - 138, // 232: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest - 127, // 233: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest - 119, // 234: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest - 139, // 235: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest - 141, // 236: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest - 143, // 237: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription - 174, // 238: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest - 179, // 239: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest - 183, // 240: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest - 186, // 241: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest - 189, // 242: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest - 192, // 243: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest - 193, // 244: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot - 195, // 245: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest - 197, // 246: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription - 200, // 247: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest - 202, // 248: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest - 204, // 249: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest - 207, // 250: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest - 213, // 251: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest - 218, // 252: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse - 25, // 253: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest - 23, // 254: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest - 66, // 255: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest - 21, // 256: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest - 115, // 257: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse - 118, // 258: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse - 31, // 259: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails - 43, // 260: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse - 47, // 261: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse - 49, // 262: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse - 29, // 263: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction - 45, // 264: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse - 51, // 265: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse - 53, // 266: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse - 55, // 267: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse - 57, // 268: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse - 59, // 269: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse - 75, // 270: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse - 77, // 271: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent - 79, // 272: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse - 81, // 273: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse - 83, // 274: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse - 110, // 275: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse - 64, // 276: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse - 112, // 277: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate - 71, // 278: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse - 38, // 279: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint - 97, // 280: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate - 95, // 281: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse - 107, // 282: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp - 36, // 283: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest - 89, // 284: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate - 173, // 285: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse - 34, // 286: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse - 34, // 287: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse - 34, // 288: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse - 34, // 289: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse - 159, // 290: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse - 162, // 291: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse - 155, // 292: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice - 155, // 293: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice - 177, // 294: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq - 167, // 295: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse - 170, // 296: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse - 171, // 297: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse - 134, // 298: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph - 136, // 299: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse - 132, // 300: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge - 128, // 301: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo - 122, // 302: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse - 140, // 303: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo - 142, // 304: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse - 144, // 305: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate - 175, // 306: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse - 181, // 307: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse - 185, // 308: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse - 188, // 309: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse - 190, // 310: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup - 193, // 311: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 198, // 312: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse - 196, // 313: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse - 193, // 314: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot - 201, // 315: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse - 203, // 316: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse - 205, // 317: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse - 208, // 318: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse - 214, // 319: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse - 215, // 320: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest - 26, // 321: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse - 24, // 322: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage - 67, // 323: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse - 22, // 324: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse - 257, // [257:325] is the sub-list for method output_type - 189, // [189:257] is the sub-list for method input_type - 189, // [189:189] is the sub-list for extension type_name - 189, // [189:189] is the sub-list for extension extendee - 0, // [0:189] is the sub-list for field type_name + 87, // 44: lnrpc.ChannelCloseUpdate.local_close_output:type_name -> lnrpc.CloseOutput + 87, // 45: lnrpc.ChannelCloseUpdate.remote_close_output:type_name -> lnrpc.CloseOutput + 87, // 46: lnrpc.ChannelCloseUpdate.additional_outputs:type_name -> lnrpc.CloseOutput + 38, // 47: lnrpc.CloseChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 91, // 48: lnrpc.CloseStatusUpdate.close_pending:type_name -> lnrpc.PendingUpdate + 88, // 49: lnrpc.CloseStatusUpdate.chan_close:type_name -> lnrpc.ChannelCloseUpdate + 92, // 50: lnrpc.CloseStatusUpdate.close_instant:type_name -> lnrpc.InstantUpdate + 95, // 51: lnrpc.BatchOpenChannelRequest.channels:type_name -> lnrpc.BatchOpenChannel + 1, // 52: lnrpc.BatchOpenChannelRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 3, // 53: lnrpc.BatchOpenChannel.commitment_type:type_name -> lnrpc.CommitmentType + 91, // 54: lnrpc.BatchOpenChannelResponse.pending_channels:type_name -> lnrpc.PendingUpdate + 103, // 55: lnrpc.OpenChannelRequest.funding_shim:type_name -> lnrpc.FundingShim + 3, // 56: lnrpc.OpenChannelRequest.commitment_type:type_name -> lnrpc.CommitmentType + 39, // 57: lnrpc.OpenChannelRequest.outpoints:type_name -> lnrpc.OutPoint + 91, // 58: lnrpc.OpenStatusUpdate.chan_pending:type_name -> lnrpc.PendingUpdate + 86, // 59: lnrpc.OpenStatusUpdate.chan_open:type_name -> lnrpc.ChannelOpenUpdate + 93, // 60: lnrpc.OpenStatusUpdate.psbt_fund:type_name -> lnrpc.ReadyForPsbtFunding + 99, // 61: lnrpc.KeyDescriptor.key_loc:type_name -> lnrpc.KeyLocator + 38, // 62: lnrpc.ChanPointShim.chan_point:type_name -> lnrpc.ChannelPoint + 100, // 63: lnrpc.ChanPointShim.local_key:type_name -> lnrpc.KeyDescriptor + 101, // 64: lnrpc.FundingShim.chan_point_shim:type_name -> lnrpc.ChanPointShim + 102, // 65: lnrpc.FundingShim.psbt_shim:type_name -> lnrpc.PsbtShim + 103, // 66: lnrpc.FundingTransitionMsg.shim_register:type_name -> lnrpc.FundingShim + 104, // 67: lnrpc.FundingTransitionMsg.shim_cancel:type_name -> lnrpc.FundingShimCancel + 105, // 68: lnrpc.FundingTransitionMsg.psbt_verify:type_name -> lnrpc.FundingPsbtVerify + 106, // 69: lnrpc.FundingTransitionMsg.psbt_finalize:type_name -> lnrpc.FundingPsbtFinalize + 229, // 70: lnrpc.PendingChannelsResponse.pending_open_channels:type_name -> lnrpc.PendingChannelsResponse.PendingOpenChannel + 232, // 71: lnrpc.PendingChannelsResponse.pending_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ClosedChannel + 233, // 72: lnrpc.PendingChannelsResponse.pending_force_closing_channels:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel + 230, // 73: lnrpc.PendingChannelsResponse.waiting_close_channels:type_name -> lnrpc.PendingChannelsResponse.WaitingCloseChannel + 62, // 74: lnrpc.ChannelEventUpdate.open_channel:type_name -> lnrpc.Channel + 68, // 75: lnrpc.ChannelEventUpdate.closed_channel:type_name -> lnrpc.ChannelCloseSummary + 38, // 76: lnrpc.ChannelEventUpdate.active_channel:type_name -> lnrpc.ChannelPoint + 38, // 77: lnrpc.ChannelEventUpdate.inactive_channel:type_name -> lnrpc.ChannelPoint + 91, // 78: lnrpc.ChannelEventUpdate.pending_open_channel:type_name -> lnrpc.PendingUpdate + 38, // 79: lnrpc.ChannelEventUpdate.fully_resolved_channel:type_name -> lnrpc.ChannelPoint + 16, // 80: lnrpc.ChannelEventUpdate.type:type_name -> lnrpc.ChannelEventUpdate.UpdateType + 234, // 81: lnrpc.WalletBalanceResponse.account_balance:type_name -> lnrpc.WalletBalanceResponse.AccountBalanceEntry + 117, // 82: lnrpc.ChannelBalanceResponse.local_balance:type_name -> lnrpc.Amount + 117, // 83: lnrpc.ChannelBalanceResponse.remote_balance:type_name -> lnrpc.Amount + 117, // 84: lnrpc.ChannelBalanceResponse.unsettled_local_balance:type_name -> lnrpc.Amount + 117, // 85: lnrpc.ChannelBalanceResponse.unsettled_remote_balance:type_name -> lnrpc.Amount + 117, // 86: lnrpc.ChannelBalanceResponse.pending_open_local_balance:type_name -> lnrpc.Amount + 117, // 87: lnrpc.ChannelBalanceResponse.pending_open_remote_balance:type_name -> lnrpc.Amount + 32, // 88: lnrpc.QueryRoutesRequest.fee_limit:type_name -> lnrpc.FeeLimit + 122, // 89: lnrpc.QueryRoutesRequest.ignored_edges:type_name -> lnrpc.EdgeLocator + 121, // 90: lnrpc.QueryRoutesRequest.ignored_pairs:type_name -> lnrpc.NodePair + 235, // 91: lnrpc.QueryRoutesRequest.dest_custom_records:type_name -> lnrpc.QueryRoutesRequest.DestCustomRecordsEntry + 151, // 92: lnrpc.QueryRoutesRequest.route_hints:type_name -> lnrpc.RouteHint + 152, // 93: lnrpc.QueryRoutesRequest.blinded_payment_paths:type_name -> lnrpc.BlindedPaymentPath + 10, // 94: lnrpc.QueryRoutesRequest.dest_features:type_name -> lnrpc.FeatureBit + 127, // 95: lnrpc.QueryRoutesResponse.routes:type_name -> lnrpc.Route + 125, // 96: lnrpc.Hop.mpp_record:type_name -> lnrpc.MPPRecord + 126, // 97: lnrpc.Hop.amp_record:type_name -> lnrpc.AMPRecord + 236, // 98: lnrpc.Hop.custom_records:type_name -> lnrpc.Hop.CustomRecordsEntry + 124, // 99: lnrpc.Route.hops:type_name -> lnrpc.Hop + 130, // 100: lnrpc.NodeInfo.node:type_name -> lnrpc.LightningNode + 133, // 101: lnrpc.NodeInfo.channels:type_name -> lnrpc.ChannelEdge + 131, // 102: lnrpc.LightningNode.addresses:type_name -> lnrpc.NodeAddress + 237, // 103: lnrpc.LightningNode.features:type_name -> lnrpc.LightningNode.FeaturesEntry + 238, // 104: lnrpc.LightningNode.custom_records:type_name -> lnrpc.LightningNode.CustomRecordsEntry + 239, // 105: lnrpc.RoutingPolicy.custom_records:type_name -> lnrpc.RoutingPolicy.CustomRecordsEntry + 132, // 106: lnrpc.ChannelEdge.node1_policy:type_name -> lnrpc.RoutingPolicy + 132, // 107: lnrpc.ChannelEdge.node2_policy:type_name -> lnrpc.RoutingPolicy + 240, // 108: lnrpc.ChannelEdge.custom_records:type_name -> lnrpc.ChannelEdge.CustomRecordsEntry + 130, // 109: lnrpc.ChannelGraph.nodes:type_name -> lnrpc.LightningNode + 133, // 110: lnrpc.ChannelGraph.edges:type_name -> lnrpc.ChannelEdge + 7, // 111: lnrpc.NodeMetricsRequest.types:type_name -> lnrpc.NodeMetricType + 241, // 112: lnrpc.NodeMetricsResponse.betweenness_centrality:type_name -> lnrpc.NodeMetricsResponse.BetweennessCentralityEntry + 146, // 113: lnrpc.GraphTopologyUpdate.node_updates:type_name -> lnrpc.NodeUpdate + 147, // 114: lnrpc.GraphTopologyUpdate.channel_updates:type_name -> lnrpc.ChannelEdgeUpdate + 148, // 115: lnrpc.GraphTopologyUpdate.closed_chans:type_name -> lnrpc.ClosedChannelUpdate + 131, // 116: lnrpc.NodeUpdate.node_addresses:type_name -> lnrpc.NodeAddress + 242, // 117: lnrpc.NodeUpdate.features:type_name -> lnrpc.NodeUpdate.FeaturesEntry + 38, // 118: lnrpc.ChannelEdgeUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 132, // 119: lnrpc.ChannelEdgeUpdate.routing_policy:type_name -> lnrpc.RoutingPolicy + 38, // 120: lnrpc.ClosedChannelUpdate.chan_point:type_name -> lnrpc.ChannelPoint + 149, // 121: lnrpc.RouteHint.hop_hints:type_name -> lnrpc.HopHint + 153, // 122: lnrpc.BlindedPaymentPath.blinded_path:type_name -> lnrpc.BlindedPath + 10, // 123: lnrpc.BlindedPaymentPath.features:type_name -> lnrpc.FeatureBit + 154, // 124: lnrpc.BlindedPath.blinded_hops:type_name -> lnrpc.BlindedHop + 8, // 125: lnrpc.AMPInvoiceState.state:type_name -> lnrpc.InvoiceHTLCState + 151, // 126: lnrpc.Invoice.route_hints:type_name -> lnrpc.RouteHint + 17, // 127: lnrpc.Invoice.state:type_name -> lnrpc.Invoice.InvoiceState + 158, // 128: lnrpc.Invoice.htlcs:type_name -> lnrpc.InvoiceHTLC + 243, // 129: lnrpc.Invoice.features:type_name -> lnrpc.Invoice.FeaturesEntry + 244, // 130: lnrpc.Invoice.amp_invoice_state:type_name -> lnrpc.Invoice.AmpInvoiceStateEntry + 157, // 131: lnrpc.Invoice.blinded_path_config:type_name -> lnrpc.BlindedPathConfig + 8, // 132: lnrpc.InvoiceHTLC.state:type_name -> lnrpc.InvoiceHTLCState + 245, // 133: lnrpc.InvoiceHTLC.custom_records:type_name -> lnrpc.InvoiceHTLC.CustomRecordsEntry + 159, // 134: lnrpc.InvoiceHTLC.amp:type_name -> lnrpc.AMP + 156, // 135: lnrpc.ListInvoiceResponse.invoices:type_name -> lnrpc.Invoice + 18, // 136: lnrpc.Payment.status:type_name -> lnrpc.Payment.PaymentStatus + 166, // 137: lnrpc.Payment.htlcs:type_name -> lnrpc.HTLCAttempt + 9, // 138: lnrpc.Payment.failure_reason:type_name -> lnrpc.PaymentFailureReason + 246, // 139: lnrpc.Payment.first_hop_custom_records:type_name -> lnrpc.Payment.FirstHopCustomRecordsEntry + 19, // 140: lnrpc.HTLCAttempt.status:type_name -> lnrpc.HTLCAttempt.HTLCStatus + 127, // 141: lnrpc.HTLCAttempt.route:type_name -> lnrpc.Route + 210, // 142: lnrpc.HTLCAttempt.failure:type_name -> lnrpc.Failure + 165, // 143: lnrpc.ListPaymentsResponse.payments:type_name -> lnrpc.Payment + 38, // 144: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint + 151, // 145: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint + 247, // 146: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry + 152, // 147: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath + 181, // 148: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport + 38, // 149: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint + 183, // 150: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee + 39, // 151: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint + 11, // 152: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure + 185, // 153: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate + 188, // 154: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent + 38, // 155: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint + 38, // 156: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint + 38, // 157: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint + 195, // 158: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups + 192, // 159: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup + 191, // 160: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup + 195, // 161: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups + 200, // 162: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission + 200, // 163: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission + 248, // 164: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry + 20, // 165: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode + 211, // 166: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate + 213, // 167: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op + 200, // 168: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission + 217, // 169: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth + 218, // 170: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage + 218, // 171: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage + 220, // 172: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration + 221, // 173: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback + 179, // 174: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature + 179, // 175: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature + 4, // 176: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator + 3, // 177: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType + 228, // 178: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 228, // 179: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 231, // 180: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments + 228, // 181: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 228, // 182: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel + 109, // 183: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC + 15, // 184: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState + 114, // 185: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance + 179, // 186: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature + 138, // 187: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric + 179, // 188: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature + 179, // 189: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature + 155, // 190: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState + 179, // 191: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature + 207, // 192: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList + 115, // 193: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest + 118, // 194: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest + 30, // 195: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest + 42, // 196: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest + 46, // 197: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest + 48, // 198: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest + 30, // 199: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest + 44, // 200: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest + 50, // 201: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest + 52, // 202: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest + 54, // 203: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest + 56, // 204: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest + 58, // 205: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest + 74, // 206: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest + 76, // 207: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription + 78, // 208: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest + 80, // 209: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest + 82, // 210: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest + 110, // 211: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest + 63, // 212: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest + 112, // 213: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription + 70, // 214: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest + 97, // 215: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest + 97, // 216: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest + 94, // 217: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest + 107, // 218: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg + 37, // 219: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse + 89, // 220: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest + 173, // 221: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest + 33, // 222: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest + 33, // 223: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest + 35, // 224: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest + 35, // 225: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest + 156, // 226: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice + 162, // 227: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest + 161, // 228: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash + 164, // 229: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription + 177, // 230: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString + 167, // 231: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest + 169, // 232: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest + 170, // 233: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest + 134, // 234: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest + 136, // 235: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest + 139, // 236: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest + 128, // 237: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest + 120, // 238: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest + 140, // 239: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest + 142, // 240: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest + 144, // 241: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription + 175, // 242: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest + 180, // 243: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest + 184, // 244: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest + 187, // 245: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest + 190, // 246: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest + 193, // 247: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest + 194, // 248: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot + 196, // 249: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest + 198, // 250: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription + 201, // 251: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest + 203, // 252: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest + 205, // 253: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest + 208, // 254: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest + 214, // 255: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest + 219, // 256: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse + 25, // 257: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest + 23, // 258: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest + 66, // 259: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest + 21, // 260: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest + 116, // 261: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse + 119, // 262: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse + 31, // 263: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails + 43, // 264: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse + 47, // 265: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse + 49, // 266: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse + 29, // 267: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction + 45, // 268: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse + 51, // 269: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse + 53, // 270: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse + 55, // 271: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse + 57, // 272: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse + 59, // 273: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse + 75, // 274: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse + 77, // 275: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent + 79, // 276: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse + 81, // 277: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse + 83, // 278: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse + 111, // 279: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse + 64, // 280: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse + 113, // 281: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate + 71, // 282: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse + 38, // 283: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint + 98, // 284: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate + 96, // 285: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse + 108, // 286: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp + 36, // 287: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest + 90, // 288: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate + 174, // 289: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse + 34, // 290: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse + 34, // 291: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse + 34, // 292: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse + 34, // 293: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse + 160, // 294: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse + 163, // 295: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse + 156, // 296: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice + 156, // 297: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice + 178, // 298: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq + 168, // 299: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse + 171, // 300: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse + 172, // 301: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse + 135, // 302: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph + 137, // 303: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse + 133, // 304: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge + 129, // 305: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo + 123, // 306: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse + 141, // 307: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo + 143, // 308: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse + 145, // 309: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate + 176, // 310: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse + 182, // 311: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse + 186, // 312: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse + 189, // 313: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse + 191, // 314: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup + 194, // 315: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 199, // 316: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse + 197, // 317: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse + 194, // 318: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot + 202, // 319: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse + 204, // 320: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse + 206, // 321: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse + 209, // 322: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse + 215, // 323: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse + 216, // 324: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest + 26, // 325: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse + 24, // 326: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage + 67, // 327: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse + 22, // 328: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse + 261, // [261:329] is the sub-list for method output_type + 193, // [193:261] is the sub-list for method input_type + 193, // [193:193] is the sub-list for extension type_name + 193, // [193:193] is the sub-list for extension extendee + 0, // [0:193] is the sub-list for field type_name } func init() { file_lightning_proto_init() } @@ -22664,7 +22906,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelCloseUpdate); i { + switch v := v.(*CloseOutput); i { case 0: return &v.state case 1: @@ -22676,7 +22918,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CloseChannelRequest); i { + switch v := v.(*ChannelCloseUpdate); i { case 0: return &v.state case 1: @@ -22688,7 +22930,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CloseStatusUpdate); i { + switch v := v.(*CloseChannelRequest); i { case 0: return &v.state case 1: @@ -22700,7 +22942,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingUpdate); i { + switch v := v.(*CloseStatusUpdate); i { case 0: return &v.state case 1: @@ -22712,7 +22954,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InstantUpdate); i { + switch v := v.(*PendingUpdate); i { case 0: return &v.state case 1: @@ -22724,7 +22966,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReadyForPsbtFunding); i { + switch v := v.(*InstantUpdate); i { case 0: return &v.state case 1: @@ -22736,7 +22978,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannelRequest); i { + switch v := v.(*ReadyForPsbtFunding); i { case 0: return &v.state case 1: @@ -22748,7 +22990,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannel); i { + switch v := v.(*BatchOpenChannelRequest); i { case 0: return &v.state case 1: @@ -22760,7 +23002,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BatchOpenChannelResponse); i { + switch v := v.(*BatchOpenChannel); i { case 0: return &v.state case 1: @@ -22772,7 +23014,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenChannelRequest); i { + switch v := v.(*BatchOpenChannelResponse); i { case 0: return &v.state case 1: @@ -22784,7 +23026,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenStatusUpdate); i { + switch v := v.(*OpenChannelRequest); i { case 0: return &v.state case 1: @@ -22796,7 +23038,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyLocator); i { + switch v := v.(*OpenStatusUpdate); i { case 0: return &v.state case 1: @@ -22808,7 +23050,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyDescriptor); i { + switch v := v.(*KeyLocator); i { case 0: return &v.state case 1: @@ -22820,7 +23062,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanPointShim); i { + switch v := v.(*KeyDescriptor); i { case 0: return &v.state case 1: @@ -22832,7 +23074,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PsbtShim); i { + switch v := v.(*ChanPointShim); i { case 0: return &v.state case 1: @@ -22844,7 +23086,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingShim); i { + switch v := v.(*PsbtShim); i { case 0: return &v.state case 1: @@ -22856,7 +23098,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingShimCancel); i { + switch v := v.(*FundingShim); i { case 0: return &v.state case 1: @@ -22868,7 +23110,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingPsbtVerify); i { + switch v := v.(*FundingShimCancel); i { case 0: return &v.state case 1: @@ -22880,7 +23122,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingPsbtFinalize); i { + switch v := v.(*FundingPsbtVerify); i { case 0: return &v.state case 1: @@ -22892,7 +23134,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingTransitionMsg); i { + switch v := v.(*FundingPsbtFinalize); i { case 0: return &v.state case 1: @@ -22904,7 +23146,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FundingStateStepResp); i { + switch v := v.(*FundingTransitionMsg); i { case 0: return &v.state case 1: @@ -22916,7 +23158,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingHTLC); i { + switch v := v.(*FundingStateStepResp); i { case 0: return &v.state case 1: @@ -22928,7 +23170,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingChannelsRequest); i { + switch v := v.(*PendingHTLC); i { case 0: return &v.state case 1: @@ -22940,7 +23182,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingChannelsResponse); i { + switch v := v.(*PendingChannelsRequest); i { case 0: return &v.state case 1: @@ -22952,7 +23194,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEventSubscription); i { + switch v := v.(*PendingChannelsResponse); i { case 0: return &v.state case 1: @@ -22964,7 +23206,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEventUpdate); i { + switch v := v.(*ChannelEventSubscription); i { case 0: return &v.state case 1: @@ -22976,7 +23218,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletAccountBalance); i { + switch v := v.(*ChannelEventUpdate); i { case 0: return &v.state case 1: @@ -22988,7 +23230,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletBalanceRequest); i { + switch v := v.(*WalletAccountBalance); i { case 0: return &v.state case 1: @@ -23000,7 +23242,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WalletBalanceResponse); i { + switch v := v.(*WalletBalanceRequest); i { case 0: return &v.state case 1: @@ -23012,7 +23254,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Amount); i { + switch v := v.(*WalletBalanceResponse); i { case 0: return &v.state case 1: @@ -23024,7 +23266,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBalanceRequest); i { + switch v := v.(*Amount); i { case 0: return &v.state case 1: @@ -23036,7 +23278,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBalanceResponse); i { + switch v := v.(*ChannelBalanceRequest); i { case 0: return &v.state case 1: @@ -23048,7 +23290,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryRoutesRequest); i { + switch v := v.(*ChannelBalanceResponse); i { case 0: return &v.state case 1: @@ -23060,7 +23302,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodePair); i { + switch v := v.(*QueryRoutesRequest); i { case 0: return &v.state case 1: @@ -23072,7 +23314,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EdgeLocator); i { + switch v := v.(*NodePair); i { case 0: return &v.state case 1: @@ -23084,7 +23326,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryRoutesResponse); i { + switch v := v.(*EdgeLocator); i { case 0: return &v.state case 1: @@ -23096,7 +23338,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Hop); i { + switch v := v.(*QueryRoutesResponse); i { case 0: return &v.state case 1: @@ -23108,7 +23350,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MPPRecord); i { + switch v := v.(*Hop); i { case 0: return &v.state case 1: @@ -23120,7 +23362,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMPRecord); i { + switch v := v.(*MPPRecord); i { case 0: return &v.state case 1: @@ -23132,7 +23374,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Route); i { + switch v := v.(*AMPRecord); i { case 0: return &v.state case 1: @@ -23144,7 +23386,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeInfoRequest); i { + switch v := v.(*Route); i { case 0: return &v.state case 1: @@ -23156,7 +23398,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeInfo); i { + switch v := v.(*NodeInfoRequest); i { case 0: return &v.state case 1: @@ -23168,7 +23410,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LightningNode); i { + switch v := v.(*NodeInfo); i { case 0: return &v.state case 1: @@ -23180,7 +23422,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeAddress); i { + switch v := v.(*LightningNode); i { case 0: return &v.state case 1: @@ -23192,7 +23434,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RoutingPolicy); i { + switch v := v.(*NodeAddress); i { case 0: return &v.state case 1: @@ -23204,7 +23446,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEdge); i { + switch v := v.(*RoutingPolicy); i { case 0: return &v.state case 1: @@ -23216,7 +23458,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelGraphRequest); i { + switch v := v.(*ChannelEdge); i { case 0: return &v.state case 1: @@ -23228,7 +23470,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelGraph); i { + switch v := v.(*ChannelGraphRequest); i { case 0: return &v.state case 1: @@ -23240,7 +23482,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeMetricsRequest); i { + switch v := v.(*ChannelGraph); i { case 0: return &v.state case 1: @@ -23252,7 +23494,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeMetricsResponse); i { + switch v := v.(*NodeMetricsRequest); i { case 0: return &v.state case 1: @@ -23264,7 +23506,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FloatMetric); i { + switch v := v.(*NodeMetricsResponse); i { case 0: return &v.state case 1: @@ -23276,7 +23518,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanInfoRequest); i { + switch v := v.(*FloatMetric); i { case 0: return &v.state case 1: @@ -23288,7 +23530,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkInfoRequest); i { + switch v := v.(*ChanInfoRequest); i { case 0: return &v.state case 1: @@ -23300,7 +23542,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetworkInfo); i { + switch v := v.(*NetworkInfoRequest); i { case 0: return &v.state case 1: @@ -23312,7 +23554,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopRequest); i { + switch v := v.(*NetworkInfo); i { case 0: return &v.state case 1: @@ -23324,7 +23566,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopResponse); i { + switch v := v.(*StopRequest); i { case 0: return &v.state case 1: @@ -23336,7 +23578,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GraphTopologySubscription); i { + switch v := v.(*StopResponse); i { case 0: return &v.state case 1: @@ -23348,7 +23590,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GraphTopologyUpdate); i { + switch v := v.(*GraphTopologySubscription); i { case 0: return &v.state case 1: @@ -23360,7 +23602,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeUpdate); i { + switch v := v.(*GraphTopologyUpdate); i { case 0: return &v.state case 1: @@ -23372,7 +23614,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelEdgeUpdate); i { + switch v := v.(*NodeUpdate); i { case 0: return &v.state case 1: @@ -23384,7 +23626,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClosedChannelUpdate); i { + switch v := v.(*ChannelEdgeUpdate); i { case 0: return &v.state case 1: @@ -23396,7 +23638,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[127].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HopHint); i { + switch v := v.(*ClosedChannelUpdate); i { case 0: return &v.state case 1: @@ -23408,7 +23650,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[128].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetID); i { + switch v := v.(*HopHint); i { case 0: return &v.state case 1: @@ -23420,7 +23662,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[129].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RouteHint); i { + switch v := v.(*SetID); i { case 0: return &v.state case 1: @@ -23432,7 +23674,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[130].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlindedPaymentPath); i { + switch v := v.(*RouteHint); i { case 0: return &v.state case 1: @@ -23444,7 +23686,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[131].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlindedPath); i { + switch v := v.(*BlindedPaymentPath); i { case 0: return &v.state case 1: @@ -23456,7 +23698,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[132].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlindedHop); i { + switch v := v.(*BlindedPath); i { case 0: return &v.state case 1: @@ -23468,7 +23710,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[133].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMPInvoiceState); i { + switch v := v.(*BlindedHop); i { case 0: return &v.state case 1: @@ -23480,7 +23722,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[134].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Invoice); i { + switch v := v.(*AMPInvoiceState); i { case 0: return &v.state case 1: @@ -23492,7 +23734,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[135].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BlindedPathConfig); i { + switch v := v.(*Invoice); i { case 0: return &v.state case 1: @@ -23504,7 +23746,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[136].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InvoiceHTLC); i { + switch v := v.(*BlindedPathConfig); i { case 0: return &v.state case 1: @@ -23516,7 +23758,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[137].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AMP); i { + switch v := v.(*InvoiceHTLC); i { case 0: return &v.state case 1: @@ -23528,7 +23770,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[138].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddInvoiceResponse); i { + switch v := v.(*AMP); i { case 0: return &v.state case 1: @@ -23540,7 +23782,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[139].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentHash); i { + switch v := v.(*AddInvoiceResponse); i { case 0: return &v.state case 1: @@ -23552,7 +23794,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[140].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListInvoiceRequest); i { + switch v := v.(*PaymentHash); i { case 0: return &v.state case 1: @@ -23564,7 +23806,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[141].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListInvoiceResponse); i { + switch v := v.(*ListInvoiceRequest); i { case 0: return &v.state case 1: @@ -23576,7 +23818,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[142].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InvoiceSubscription); i { + switch v := v.(*ListInvoiceResponse); i { case 0: return &v.state case 1: @@ -23588,7 +23830,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[143].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Payment); i { + switch v := v.(*InvoiceSubscription); i { case 0: return &v.state case 1: @@ -23600,7 +23842,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[144].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTLCAttempt); i { + switch v := v.(*Payment); i { case 0: return &v.state case 1: @@ -23612,7 +23854,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[145].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPaymentsRequest); i { + switch v := v.(*HTLCAttempt); i { case 0: return &v.state case 1: @@ -23624,7 +23866,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[146].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPaymentsResponse); i { + switch v := v.(*ListPaymentsRequest); i { case 0: return &v.state case 1: @@ -23636,7 +23878,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[147].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeletePaymentRequest); i { + switch v := v.(*ListPaymentsResponse); i { case 0: return &v.state case 1: @@ -23648,7 +23890,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[148].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAllPaymentsRequest); i { + switch v := v.(*DeletePaymentRequest); i { case 0: return &v.state case 1: @@ -23660,7 +23902,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[149].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeletePaymentResponse); i { + switch v := v.(*DeleteAllPaymentsRequest); i { case 0: return &v.state case 1: @@ -23672,7 +23914,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[150].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteAllPaymentsResponse); i { + switch v := v.(*DeletePaymentResponse); i { case 0: return &v.state case 1: @@ -23684,7 +23926,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[151].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AbandonChannelRequest); i { + switch v := v.(*DeleteAllPaymentsResponse); i { case 0: return &v.state case 1: @@ -23696,7 +23938,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[152].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AbandonChannelResponse); i { + switch v := v.(*AbandonChannelRequest); i { case 0: return &v.state case 1: @@ -23708,7 +23950,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[153].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugLevelRequest); i { + switch v := v.(*AbandonChannelResponse); i { case 0: return &v.state case 1: @@ -23720,7 +23962,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[154].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugLevelResponse); i { + switch v := v.(*DebugLevelRequest); i { case 0: return &v.state case 1: @@ -23732,7 +23974,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[155].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PayReqString); i { + switch v := v.(*DebugLevelResponse); i { case 0: return &v.state case 1: @@ -23744,7 +23986,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[156].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PayReq); i { + switch v := v.(*PayReqString); i { case 0: return &v.state case 1: @@ -23756,7 +23998,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[157].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Feature); i { + switch v := v.(*PayReq); i { case 0: return &v.state case 1: @@ -23768,7 +24010,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[158].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeeReportRequest); i { + switch v := v.(*Feature); i { case 0: return &v.state case 1: @@ -23780,7 +24022,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[159].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelFeeReport); i { + switch v := v.(*FeeReportRequest); i { case 0: return &v.state case 1: @@ -23792,7 +24034,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[160].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FeeReportResponse); i { + switch v := v.(*ChannelFeeReport); i { case 0: return &v.state case 1: @@ -23804,7 +24046,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[161].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InboundFee); i { + switch v := v.(*FeeReportResponse); i { case 0: return &v.state case 1: @@ -23816,7 +24058,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[162].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PolicyUpdateRequest); i { + switch v := v.(*InboundFee); i { case 0: return &v.state case 1: @@ -23828,7 +24070,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[163].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedUpdate); i { + switch v := v.(*PolicyUpdateRequest); i { case 0: return &v.state case 1: @@ -23840,7 +24082,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[164].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PolicyUpdateResponse); i { + switch v := v.(*FailedUpdate); i { case 0: return &v.state case 1: @@ -23852,7 +24094,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[165].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingHistoryRequest); i { + switch v := v.(*PolicyUpdateResponse); i { case 0: return &v.state case 1: @@ -23864,7 +24106,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[166].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingEvent); i { + switch v := v.(*ForwardingHistoryRequest); i { case 0: return &v.state case 1: @@ -23876,7 +24118,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[167].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForwardingHistoryResponse); i { + switch v := v.(*ForwardingEvent); i { case 0: return &v.state case 1: @@ -23888,7 +24130,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[168].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExportChannelBackupRequest); i { + switch v := v.(*ForwardingHistoryResponse); i { case 0: return &v.state case 1: @@ -23900,7 +24142,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[169].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackup); i { + switch v := v.(*ExportChannelBackupRequest); i { case 0: return &v.state case 1: @@ -23912,7 +24154,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[170].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MultiChanBackup); i { + switch v := v.(*ChannelBackup); i { case 0: return &v.state case 1: @@ -23924,7 +24166,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[171].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanBackupExportRequest); i { + switch v := v.(*MultiChanBackup); i { case 0: return &v.state case 1: @@ -23936,7 +24178,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[172].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChanBackupSnapshot); i { + switch v := v.(*ChanBackupExportRequest); i { case 0: return &v.state case 1: @@ -23948,7 +24190,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[173].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackups); i { + switch v := v.(*ChanBackupSnapshot); i { case 0: return &v.state case 1: @@ -23960,7 +24202,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[174].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestoreChanBackupRequest); i { + switch v := v.(*ChannelBackups); i { case 0: return &v.state case 1: @@ -23972,7 +24214,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[175].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestoreBackupResponse); i { + switch v := v.(*RestoreChanBackupRequest); i { case 0: return &v.state case 1: @@ -23984,7 +24226,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[176].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelBackupSubscription); i { + switch v := v.(*RestoreBackupResponse); i { case 0: return &v.state case 1: @@ -23996,7 +24238,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[177].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VerifyChanBackupResponse); i { + switch v := v.(*ChannelBackupSubscription); i { case 0: return &v.state case 1: @@ -24008,7 +24250,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[178].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonPermission); i { + switch v := v.(*VerifyChanBackupResponse); i { case 0: return &v.state case 1: @@ -24020,7 +24262,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[179].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BakeMacaroonRequest); i { + switch v := v.(*MacaroonPermission); i { case 0: return &v.state case 1: @@ -24032,7 +24274,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[180].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BakeMacaroonResponse); i { + switch v := v.(*BakeMacaroonRequest); i { case 0: return &v.state case 1: @@ -24044,7 +24286,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[181].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMacaroonIDsRequest); i { + switch v := v.(*BakeMacaroonResponse); i { case 0: return &v.state case 1: @@ -24056,7 +24298,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[182].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMacaroonIDsResponse); i { + switch v := v.(*ListMacaroonIDsRequest); i { case 0: return &v.state case 1: @@ -24068,7 +24310,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[183].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteMacaroonIDRequest); i { + switch v := v.(*ListMacaroonIDsResponse); i { case 0: return &v.state case 1: @@ -24080,7 +24322,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[184].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteMacaroonIDResponse); i { + switch v := v.(*DeleteMacaroonIDRequest); i { case 0: return &v.state case 1: @@ -24092,7 +24334,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[185].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonPermissionList); i { + switch v := v.(*DeleteMacaroonIDResponse); i { case 0: return &v.state case 1: @@ -24104,7 +24346,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[186].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPermissionsRequest); i { + switch v := v.(*MacaroonPermissionList); i { case 0: return &v.state case 1: @@ -24116,7 +24358,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[187].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPermissionsResponse); i { + switch v := v.(*ListPermissionsRequest); i { case 0: return &v.state case 1: @@ -24128,7 +24370,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[188].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Failure); i { + switch v := v.(*ListPermissionsResponse); i { case 0: return &v.state case 1: @@ -24140,7 +24382,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[189].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChannelUpdate); i { + switch v := v.(*Failure); i { case 0: return &v.state case 1: @@ -24152,7 +24394,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[190].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MacaroonId); i { + switch v := v.(*ChannelUpdate); i { case 0: return &v.state case 1: @@ -24164,7 +24406,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[191].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Op); i { + switch v := v.(*MacaroonId); i { case 0: return &v.state case 1: @@ -24176,7 +24418,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[192].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckMacPermRequest); i { + switch v := v.(*Op); i { case 0: return &v.state case 1: @@ -24188,7 +24430,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[193].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckMacPermResponse); i { + switch v := v.(*CheckMacPermRequest); i { case 0: return &v.state case 1: @@ -24200,7 +24442,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[194].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMiddlewareRequest); i { + switch v := v.(*CheckMacPermResponse); i { case 0: return &v.state case 1: @@ -24212,7 +24454,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[195].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamAuth); i { + switch v := v.(*RPCMiddlewareRequest); i { case 0: return &v.state case 1: @@ -24224,7 +24466,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[196].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMessage); i { + switch v := v.(*StreamAuth); i { case 0: return &v.state case 1: @@ -24236,7 +24478,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[197].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCMiddlewareResponse); i { + switch v := v.(*RPCMessage); i { case 0: return &v.state case 1: @@ -24248,7 +24490,7 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[198].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MiddlewareRegistration); i { + switch v := v.(*RPCMiddlewareResponse); i { case 0: return &v.state case 1: @@ -24260,6 +24502,18 @@ func file_lightning_proto_init() { } } file_lightning_proto_msgTypes[199].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MiddlewareRegistration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightning_proto_msgTypes[200].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InterceptFeedback); i { case 0: return &v.state @@ -24271,7 +24525,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[206].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[207].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_PendingChannel); i { case 0: return &v.state @@ -24283,7 +24537,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[207].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[208].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_PendingOpenChannel); i { case 0: return &v.state @@ -24295,7 +24549,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[208].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[209].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_WaitingCloseChannel); i { case 0: return &v.state @@ -24307,7 +24561,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[209].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[210].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_Commitments); i { case 0: return &v.state @@ -24319,7 +24573,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[210].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[211].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_ClosedChannel); i { case 0: return &v.state @@ -24331,7 +24585,7 @@ func file_lightning_proto_init() { return nil } } - file_lightning_proto_msgTypes[211].Exporter = func(v interface{}, i int) interface{} { + file_lightning_proto_msgTypes[212].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PendingChannelsResponse_ForceClosedChannel); i { case 0: return &v.state @@ -24353,27 +24607,27 @@ func file_lightning_proto_init() { (*ChannelPoint_FundingTxidBytes)(nil), (*ChannelPoint_FundingTxidStr)(nil), } - file_lightning_proto_msgTypes[68].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[69].OneofWrappers = []interface{}{ (*CloseStatusUpdate_ClosePending)(nil), (*CloseStatusUpdate_ChanClose)(nil), (*CloseStatusUpdate_CloseInstant)(nil), } - file_lightning_proto_msgTypes[76].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[77].OneofWrappers = []interface{}{ (*OpenStatusUpdate_ChanPending)(nil), (*OpenStatusUpdate_ChanOpen)(nil), (*OpenStatusUpdate_PsbtFund)(nil), } - file_lightning_proto_msgTypes[81].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[82].OneofWrappers = []interface{}{ (*FundingShim_ChanPointShim)(nil), (*FundingShim_PsbtShim)(nil), } - file_lightning_proto_msgTypes[85].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[86].OneofWrappers = []interface{}{ (*FundingTransitionMsg_ShimRegister)(nil), (*FundingTransitionMsg_ShimCancel)(nil), (*FundingTransitionMsg_PsbtVerify)(nil), (*FundingTransitionMsg_PsbtFinalize)(nil), } - file_lightning_proto_msgTypes[91].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[92].OneofWrappers = []interface{}{ (*ChannelEventUpdate_OpenChannel)(nil), (*ChannelEventUpdate_ClosedChannel)(nil), (*ChannelEventUpdate_ActiveChannel)(nil), @@ -24381,22 +24635,22 @@ func file_lightning_proto_init() { (*ChannelEventUpdate_PendingOpenChannel)(nil), (*ChannelEventUpdate_FullyResolvedChannel)(nil), } - file_lightning_proto_msgTypes[135].OneofWrappers = []interface{}{} - file_lightning_proto_msgTypes[162].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[136].OneofWrappers = []interface{}{} + file_lightning_proto_msgTypes[163].OneofWrappers = []interface{}{ (*PolicyUpdateRequest_Global)(nil), (*PolicyUpdateRequest_ChanPoint)(nil), } - file_lightning_proto_msgTypes[174].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[175].OneofWrappers = []interface{}{ (*RestoreChanBackupRequest_ChanBackups)(nil), (*RestoreChanBackupRequest_MultiChanBackup)(nil), } - file_lightning_proto_msgTypes[194].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[195].OneofWrappers = []interface{}{ (*RPCMiddlewareRequest_StreamAuth)(nil), (*RPCMiddlewareRequest_Request)(nil), (*RPCMiddlewareRequest_Response)(nil), (*RPCMiddlewareRequest_RegComplete)(nil), } - file_lightning_proto_msgTypes[197].OneofWrappers = []interface{}{ + file_lightning_proto_msgTypes[198].OneofWrappers = []interface{}{ (*RPCMiddlewareResponse_Register)(nil), (*RPCMiddlewareResponse_Feedback)(nil), } @@ -24406,7 +24660,7 @@ func file_lightning_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lightning_proto_rawDesc, NumEnums: 21, - NumMessages: 226, + NumMessages: 228, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 201bb0df2b..6449305159 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 { @@ -1592,6 +1598,11 @@ message Channel { the channel's operation. */ string memo = 36; + + /* + Custom channel data that might be populated in custom channels. + */ + bytes custom_channel_data = 37; } message ListChannelsRequest { @@ -2028,10 +2039,38 @@ message ChannelOpenUpdate { ChannelPoint channel_point = 1; } +message CloseOutput { + // The amount in satoshi of this close output. This amount is the final + // commitment balance of the channel and the actual amount paid out on chain + // might be smaller due to subtracted fees. + int64 amount_sat = 1; + + // The pkScript of the close output. + bytes pk_script = 2; + + // Whether this output is for the local or remote node. + bool is_local = 3; + + // The TLV encoded custom channel data records for this output, which might + // be set for custom channels. + bytes custom_channel_data = 4; +} + message ChannelCloseUpdate { bytes closing_txid = 1; bool success = 2; + + // The local channel close output. If the local channel balance was dust to + // begin with, this output will not be set. + CloseOutput local_close_output = 3; + + // The remote channel close output. If the remote channel balance was dust + // to begin with, this output will not be set. + CloseOutput remote_close_output = 4; + + // Any additional outputs that might be added for custom channel types. + repeated CloseOutput additional_outputs = 5; } message CloseChannelRequest { @@ -2709,6 +2748,11 @@ message PendingChannelsResponse { impacts the channel's operation. */ string memo = 13; + + /* + Custom channel data that might be populated in custom channels. + */ + bytes custom_channel_data = 34; } message PendingOpenChannel { @@ -2968,6 +3012,12 @@ message ChannelBalanceResponse { // Sum of channels pending remote balances. Amount pending_open_remote_balance = 8; + + /* + Custom channel data that might be populated if there are custom channels + present. + */ + bytes custom_channel_data = 9; } message QueryRoutesRequest { @@ -3293,6 +3343,20 @@ message Route { The total amount in millisatoshis. */ int64 total_amt_msat = 6; + + /* + The actual on-chain amount that was sent out to the first hop. This value is + only different from the total_amt_msat field if this is a custom channel + payment and the value transported in the HTLC is different from the BTC + amount in the HTLC. If this value is zero, then this is an old payment that + didn't have this value yet and can be ignored. + */ + int64 first_hop_amount_msat = 7; + + /* + Custom channel data that might be populated in custom channels. + */ + bytes custom_channel_data = 8; } message NodeInfoRequest { @@ -3922,6 +3986,11 @@ message InvoiceHTLC { // Details relevant to AMP HTLCs, only populated if this is an AMP HTLC. AMP amp = 11; + + /* + Custom channel data that might be populated in custom channels. + */ + bytes custom_channel_data = 12; } // Details specific to AMP HTLCs. @@ -4162,6 +4231,12 @@ message Payment { uint64 payment_index = 15; PaymentFailureReason failure_reason = 16; + + /* + The custom TLV records that were sent to the first hop as part of the HTLC + wire message for this payment. + */ + map first_hop_custom_records = 17; } message HTLCAttempt { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index bcd9e438b0..5531d1ebb0 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -3127,6 +3127,11 @@ "memo": { "type": "string", "description": "An optional note-to-self to go along with the channel containing some\nuseful information. This is only ever stored locally and in no way\nimpacts the channel's operation." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } } }, @@ -3849,6 +3854,11 @@ "memo": { "type": "string", "description": "An optional note-to-self to go along with the channel containing some\nuseful information. This is only ever stored locally and in no way impacts\nthe channel's operation." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } } }, @@ -4052,6 +4062,11 @@ "pending_open_remote_balance": { "$ref": "#/definitions/lnrpcAmount", "description": "Sum of channels pending remote balances." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated if there are custom channels\npresent." } } }, @@ -4141,6 +4156,21 @@ }, "success": { "type": "boolean" + }, + "local_close_output": { + "$ref": "#/definitions/lnrpcCloseOutput", + "description": "The local channel close output. If the local channel balance was dust to\nbegin with, this output will not be set." + }, + "remote_close_output": { + "$ref": "#/definitions/lnrpcCloseOutput", + "description": "The remote channel close output. If the remote channel balance was dust\nto begin with, this output will not be set." + }, + "additional_outputs": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcCloseOutput" + }, + "description": "Any additional outputs that might be added for custom channel types." } } }, @@ -4450,6 +4480,30 @@ } } }, + "lnrpcCloseOutput": { + "type": "object", + "properties": { + "amount_sat": { + "type": "string", + "format": "int64", + "description": "The amount in satoshi of this close output. This amount is the final\ncommitment balance of the channel and the actual amount paid out on chain\nmight be smaller due to subtracted fees." + }, + "pk_script": { + "type": "string", + "format": "byte", + "description": "The pkScript of the close output." + }, + "is_local": { + "type": "boolean", + "description": "Whether this output is for the local or remote node." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "The TLV encoded custom channel data records for this output, which might\nbe set for custom channels." + } + } + }, "lnrpcCloseStatusUpdate": { "type": "object", "properties": { @@ -4514,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", @@ -5587,6 +5642,11 @@ "amp": { "$ref": "#/definitions/lnrpcAMP", "description": "Details relevant to AMP HTLCs, only populated if this is an AMP HTLC." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } }, "title": "Details of an HTLC that paid to an invoice" @@ -6417,6 +6477,14 @@ }, "failure_reason": { "$ref": "#/definitions/lnrpcPaymentFailureReason" + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "The custom TLV records that were sent to the first hop as part of the HTLC\nwire message for this payment." } } }, @@ -6904,6 +6972,16 @@ "type": "string", "format": "int64", "description": "The total amount in millisatoshis." + }, + "first_hop_amount_msat": { + "type": "string", + "format": "int64", + "description": "The actual on-chain amount that was sent out to the first hop. This value is\nonly different from the total_amt_msat field if this is a custom channel\npayment and the value transported in the HTLC is different from the BTC\namount in the HTLC. If this value is zero, then this is an old payment that\ndidn't have this value yet and can be ignored." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } }, "description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for." diff --git a/lnrpc/marshall_utils.go b/lnrpc/marshall_utils.go index 2e86d8eca4..0dcbe46fab 100644 --- a/lnrpc/marshall_utils.go +++ b/lnrpc/marshall_utils.go @@ -3,15 +3,18 @@ package lnrpc import ( "encoding/hex" "errors" - fmt "fmt" + "fmt" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" + "github.com/lightningnetwork/lnd/aliasmgr" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "golang.org/x/exp/maps" ) var ( @@ -207,3 +210,16 @@ func UnmarshallCoinSelectionStrategy(strategy CoinSelectionStrategy, "%v", strategy) } } + +// MarshalAliasMap converts a ScidAliasMap to its proto counterpart. This is +// used in various RPCs that handle scid alias mappings. +func MarshalAliasMap(scidMap aliasmgr.ScidAliasMap) []*AliasMap { + return fn.Map(func(base lnwire.ShortChannelID) *AliasMap { + return &AliasMap{ + BaseScid: base.ToUint64(), + Aliases: fn.Map(func(a lnwire.ShortChannelID) uint64 { + return a.ToUint64() + }, scidMap[base]), + } + }, maps.Keys(scidMap)) +} diff --git a/lnrpc/routerrpc/config.go b/lnrpc/routerrpc/config.go index 7b6d145999..20e3fa09bf 100644 --- a/lnrpc/routerrpc/config.go +++ b/lnrpc/routerrpc/config.go @@ -1,6 +1,7 @@ package routerrpc import ( + "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/routing" ) @@ -46,6 +47,10 @@ type Config struct { // RouterBackend contains shared logic between this sub server and the // main rpc server. RouterBackend *RouterBackend + + // AliasMgr is the alias manager instance that is used to handle all the + // SCID alias related information for channels. + AliasMgr *aliasmgr.Manager } // DefaultConfig defines the config defaults. diff --git a/lnrpc/routerrpc/forward_interceptor.go b/lnrpc/routerrpc/forward_interceptor.go index 8af1a21f6c..614a11888c 100644 --- a/lnrpc/routerrpc/forward_interceptor.go +++ b/lnrpc/routerrpc/forward_interceptor.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -22,7 +23,7 @@ var ( ErrMissingPreimage = errors.New("missing preimage") ) -// forwardInterceptor is a helper struct that handles the lifecycle of an rpc +// forwardInterceptor is a helper struct that handles the lifecycle of an RPC // interceptor streaming session. // It is created when the stream opens and disconnects when the stream closes. type forwardInterceptor struct { @@ -43,7 +44,7 @@ func newForwardInterceptor(htlcSwitch htlcswitch.InterceptableHtlcForwarder, } // run sends the intercepted packets to the client and receives the -// corersponding responses. On one hand it registered itself as an interceptor +// corresponding responses. On one hand it registered itself as an interceptor // that receives the switch packets and on the other hand launches a go routine // to read from the client stream. // To coordinate all this and make sure it is safe for concurrent access all @@ -88,9 +89,10 @@ func (r *forwardInterceptor) onIntercept( OutgoingExpiry: htlc.OutgoingExpiry, IncomingAmountMsat: uint64(htlc.IncomingAmount), IncomingExpiry: htlc.IncomingExpiry, - CustomRecords: htlc.CustomRecords, + CustomRecords: htlc.InOnionCustomRecords, OnionBlob: htlc.OnionBlob[:], AutoFailHeight: htlc.AutoFailHeight, + InWireCustomRecords: htlc.InWireCustomRecords, } return r.stream.Send(interceptionRequest) @@ -108,7 +110,9 @@ func (r *forwardInterceptor) resolveFromClient( log.Tracef("Resolving intercepted packet %v", in) circuitKey := models.CircuitKey{ - ChanID: lnwire.NewShortChanIDFromInt(in.IncomingCircuitKey.ChanId), + ChanID: lnwire.NewShortChanIDFromInt( + in.IncomingCircuitKey.ChanId, + ), HtlcID: in.IncomingCircuitKey.HtlcId, } @@ -119,6 +123,46 @@ func (r *forwardInterceptor) resolveFromClient( Action: htlcswitch.FwdActionResume, }) + case ResolveHoldForwardAction_RESUME_MODIFIED: + // Modify HTLC and resume forward. + inAmtMsat := fn.None[lnwire.MilliSatoshi]() + if in.InAmountMsat > 0 { + inAmtMsat = fn.Some(lnwire.MilliSatoshi( + in.InAmountMsat, + )) + } + + outAmtMsat := fn.None[lnwire.MilliSatoshi]() + if in.OutAmountMsat > 0 { + outAmtMsat = fn.Some(lnwire.MilliSatoshi( + in.OutAmountMsat, + )) + } + + outWireCustomRecords := fn.None[lnwire.CustomRecords]() + if len(in.OutWireCustomRecords) > 0 { + // Validate custom records. + cr := lnwire.CustomRecords(in.OutWireCustomRecords) + if err := cr.Validate(); err != nil { + return status.Errorf( + codes.InvalidArgument, + "failed to validate custom records: %v", + err, + ) + } + + outWireCustomRecords = fn.Some[lnwire.CustomRecords](cr) + } + + //nolint:lll + return r.htlcSwitch.Resolve(&htlcswitch.FwdResolution{ + Key: circuitKey, + Action: htlcswitch.FwdActionResumeModified, + InAmountMsat: inAmtMsat, + OutAmountMsat: outAmtMsat, + OutWireCustomRecords: outWireCustomRecords, + }) + case ResolveHoldForwardAction_FAIL: // Fail with an encrypted reason. if in.FailureMessage != nil { diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 7b91c445e8..f74fb9d3fc 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -203,9 +203,16 @@ func (PaymentState) EnumDescriptor() ([]byte, []int) { type ResolveHoldForwardAction int32 const ( + // SETTLE is an action that is used to settle an HTLC instead of forwarding + // it. ResolveHoldForwardAction_SETTLE ResolveHoldForwardAction = 0 - ResolveHoldForwardAction_FAIL ResolveHoldForwardAction = 1 + // FAIL is an action that is used to fail an HTLC backwards. + ResolveHoldForwardAction_FAIL ResolveHoldForwardAction = 1 + // RESUME is an action that is used to resume a forward HTLC. ResolveHoldForwardAction_RESUME ResolveHoldForwardAction = 2 + // RESUME_MODIFIED is an action that is used to resume a hold forward HTLC + // with modifications specified during interception. + ResolveHoldForwardAction_RESUME_MODIFIED ResolveHoldForwardAction = 3 ) // Enum value maps for ResolveHoldForwardAction. @@ -214,11 +221,13 @@ var ( 0: "SETTLE", 1: "FAIL", 2: "RESUME", + 3: "RESUME_MODIFIED", } ResolveHoldForwardAction_value = map[string]int32{ - "SETTLE": 0, - "FAIL": 1, - "RESUME": 2, + "SETTLE": 0, + "FAIL": 1, + "RESUME": 2, + "RESUME_MODIFIED": 3, } ) @@ -498,6 +507,12 @@ type SendPaymentRequest struct { // still settle afterwards. Canceling will only prevent further attempts from // being sent. Cancelable bool `protobuf:"varint,24,opt,name=cancelable,proto3" json:"cancelable,omitempty"` + // An optional field that can be used to pass an arbitrary set of TLV records + // to the first hop peer of this payment. This can be used to pass application + // specific data during the payment attempt. Record types are required to be in + // the custom range >= 65536. When using REST, the values must be encoded as + // base64. + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,25,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SendPaymentRequest) Reset() { @@ -701,6 +716,13 @@ func (x *SendPaymentRequest) GetCancelable() bool { return false } +func (x *SendPaymentRequest) GetFirstHopCustomRecords() map[uint64][]byte { + if x != nil { + return x.FirstHopCustomRecords + } + return nil +} + type TrackPaymentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -982,6 +1004,12 @@ type SendToRouteRequest struct { // failed unless a terminal error is occurred, such as payment timeout, no // routes, incorrect payment details, or insufficient funds. SkipTempErr bool `protobuf:"varint,3,opt,name=skip_temp_err,json=skipTempErr,proto3" json:"skip_temp_err,omitempty"` + // An optional field that can be used to pass an arbitrary set of TLV records + // to the first hop peer of this payment. This can be used to pass application + // specific data during the payment attempt. Record types are required to be in + // the custom range >= 65536. When using REST, the values must be encoded as + // base64. + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,4,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *SendToRouteRequest) Reset() { @@ -1037,6 +1065,13 @@ func (x *SendToRouteRequest) GetSkipTempErr() bool { return false } +func (x *SendToRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte { + if x != nil { + return x.FirstHopCustomRecords + } + return nil +} + type SendToRouteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2158,6 +2193,12 @@ type BuildRouteRequest struct { // An optional payment addr to be included within the last hop of the route. // This is also called payment secret in specifications (e.g. BOLT 11). PaymentAddr []byte `protobuf:"bytes,5,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` + // An optional field that can be used to pass an arbitrary set of TLV records + // to the first hop peer of this payment. This can be used to pass application + // specific data during the payment attempt. Record types are required to be in + // the custom range >= 65536. When using REST, the values must be encoded as + // base64. + FirstHopCustomRecords map[uint64][]byte `protobuf:"bytes,6,rep,name=first_hop_custom_records,json=firstHopCustomRecords,proto3" json:"first_hop_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *BuildRouteRequest) Reset() { @@ -2227,6 +2268,13 @@ func (x *BuildRouteRequest) GetPaymentAddr() []byte { return nil } +func (x *BuildRouteRequest) GetFirstHopCustomRecords() map[uint64][]byte { + if x != nil { + return x.FirstHopCustomRecords + } + return nil +} + type BuildRouteResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3048,6 +3096,8 @@ type ForwardHtlcInterceptRequest struct { // The block height at which this htlc will be auto-failed to prevent the // channel from force-closing. AutoFailHeight int32 `protobuf:"varint,10,opt,name=auto_fail_height,json=autoFailHeight,proto3" json:"auto_fail_height,omitempty"` + // The custom records of the peer's incoming p2p wire message. + InWireCustomRecords map[uint64][]byte `protobuf:"bytes,11,rep,name=in_wire_custom_records,json=inWireCustomRecords,proto3" json:"in_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ForwardHtlcInterceptRequest) Reset() { @@ -3152,10 +3202,19 @@ func (x *ForwardHtlcInterceptRequest) GetAutoFailHeight() int32 { return 0 } +func (x *ForwardHtlcInterceptRequest) GetInWireCustomRecords() map[uint64][]byte { + if x != nil { + return x.InWireCustomRecords + } + return nil +} + // * // ForwardHtlcInterceptResponse enables the caller to resolve a previously hold // forward. The caller can choose either to: // - `Resume`: Execute the default behavior (usually forward). +// - `ResumeModified`: Execute the default behavior (usually forward) with HTLC +// field modifications. // - `Reject`: Fail the htlc backwards. // - `Settle`: Settle this htlc with a given preimage. type ForwardHtlcInterceptResponse struct { @@ -3184,6 +3243,17 @@ type ForwardHtlcInterceptResponse struct { // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the // default value for this field. FailureCode lnrpc.Failure_FailureCode `protobuf:"varint,5,opt,name=failure_code,json=failureCode,proto3,enum=lnrpc.Failure_FailureCode" json:"failure_code,omitempty"` + // The amount that was set on the p2p wire message of the incoming HTLC. + // This field is ignored if the action is not RESUME_MODIFIED or the amount + // is zero. + InAmountMsat uint64 `protobuf:"varint,6,opt,name=in_amount_msat,json=inAmountMsat,proto3" json:"in_amount_msat,omitempty"` + // The amount to set on the p2p wire message of the resumed HTLC. This field + // is ignored if the action is not RESUME_MODIFIED or the amount is zero. + OutAmountMsat uint64 `protobuf:"varint,7,opt,name=out_amount_msat,json=outAmountMsat,proto3" json:"out_amount_msat,omitempty"` + // Any custom records that should be set on the p2p wire message message of + // the resumed HTLC. This field is ignored if the action is not + // RESUME_MODIFIED. + OutWireCustomRecords map[uint64][]byte `protobuf:"bytes,8,rep,name=out_wire_custom_records,json=outWireCustomRecords,proto3" json:"out_wire_custom_records,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ForwardHtlcInterceptResponse) Reset() { @@ -3253,6 +3323,27 @@ func (x *ForwardHtlcInterceptResponse) GetFailureCode() lnrpc.Failure_FailureCod return lnrpc.Failure_FailureCode(0) } +func (x *ForwardHtlcInterceptResponse) GetInAmountMsat() uint64 { + if x != nil { + return x.InAmountMsat + } + return 0 +} + +func (x *ForwardHtlcInterceptResponse) GetOutAmountMsat() uint64 { + if x != nil { + return x.OutAmountMsat + } + return 0 +} + +func (x *ForwardHtlcInterceptResponse) GetOutWireCustomRecords() map[uint64][]byte { + if x != nil { + return x.OutWireCustomRecords + } + return nil +} + type UpdateChanStatusRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3346,13 +3437,201 @@ func (*UpdateChanStatusResponse) Descriptor() ([]byte, []int) { return file_routerrpc_router_proto_rawDescGZIP(), []int{40} } +type AddAliasesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` +} + +func (x *AddAliasesRequest) Reset() { + *x = AddAliasesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddAliasesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddAliasesRequest) ProtoMessage() {} + +func (x *AddAliasesRequest) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddAliasesRequest.ProtoReflect.Descriptor instead. +func (*AddAliasesRequest) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{41} +} + +func (x *AddAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { + if x != nil { + return x.AliasMaps + } + return nil +} + +type AddAliasesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` +} + +func (x *AddAliasesResponse) Reset() { + *x = AddAliasesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddAliasesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddAliasesResponse) ProtoMessage() {} + +func (x *AddAliasesResponse) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddAliasesResponse.ProtoReflect.Descriptor instead. +func (*AddAliasesResponse) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{42} +} + +func (x *AddAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { + if x != nil { + return x.AliasMaps + } + return nil +} + +type DeleteAliasesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` +} + +func (x *DeleteAliasesRequest) Reset() { + *x = DeleteAliasesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAliasesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAliasesRequest) ProtoMessage() {} + +func (x *DeleteAliasesRequest) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAliasesRequest.ProtoReflect.Descriptor instead. +func (*DeleteAliasesRequest) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{43} +} + +func (x *DeleteAliasesRequest) GetAliasMaps() []*lnrpc.AliasMap { + if x != nil { + return x.AliasMaps + } + return nil +} + +type DeleteAliasesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AliasMaps []*lnrpc.AliasMap `protobuf:"bytes,1,rep,name=alias_maps,json=aliasMaps,proto3" json:"alias_maps,omitempty"` +} + +func (x *DeleteAliasesResponse) Reset() { + *x = DeleteAliasesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_routerrpc_router_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAliasesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAliasesResponse) ProtoMessage() {} + +func (x *DeleteAliasesResponse) ProtoReflect() protoreflect.Message { + mi := &file_routerrpc_router_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAliasesResponse.ProtoReflect.Descriptor instead. +func (*DeleteAliasesResponse) Descriptor() ([]byte, []int) { + return file_routerrpc_router_proto_rawDescGZIP(), []int{44} +} + +func (x *DeleteAliasesResponse) GetAliasMaps() []*lnrpc.AliasMap { + if x != nil { + return x.AliasMaps + } + return nil +} + var File_routerrpc_router_proto protoreflect.FileDescriptor var file_routerrpc_router_proto_rawDesc = []byte{ 0x0a, 0x16, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x08, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x09, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x61, 0x6d, @@ -3413,518 +3692,613 @@ var file_routerrpc_router_proto_rawDesc = []byte{ 0x5f, 0x70, 0x72, 0x65, 0x66, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x65, 0x66, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, 0x43, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x54, - 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, - 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, - 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x81, 0x01, - 0x0a, 0x0f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x27, - 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, - 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, - 0x67, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, - 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, - 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x7f, 0x0a, 0x12, - 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, - 0x70, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, 0x6d, 0x70, 0x45, 0x72, 0x72, 0x22, 0x5b, 0x0a, - 0x13, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, - 0x12, 0x28, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, - 0x65, 0x52, 0x07, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, - 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, - 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, - 0x72, 0x73, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, - 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x1f, 0x0a, 0x1d, - 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, - 0x0a, 0x0b, 0x50, 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, - 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, - 0x64, 0x65, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, - 0x65, 0x54, 0x6f, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, - 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, - 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, - 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, - 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, - 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, - 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0b, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, - 0x0f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, - 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, - 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0x59, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x71, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, + 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x44, 0x0a, 0x16, 0x44, 0x65, 0x73, 0x74, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x48, + 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x6f, + 0x5f, 0x69, 0x6e, 0x66, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x49, 0x6e, 0x66, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x74, 0x53, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0xa8, + 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x6b, + 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xbc, 0x02, 0x0a, 0x12, 0x53, 0x65, + 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, + 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, + 0x73, 0x6b, 0x69, 0x70, 0x54, 0x65, 0x6d, 0x70, 0x45, 0x72, 0x72, 0x12, 0x71, 0x0a, 0x18, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, + 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, + 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, + 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x07, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x51, 0x0a, 0x1b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x4a, 0x04, 0x08, + 0x01, 0x10, 0x02, 0x22, 0x62, 0x0a, 0x1c, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x61, 0x69, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x22, 0x1f, 0x0a, 0x1d, 0x58, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x50, 0x61, 0x69, + 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x6f, 0x64, + 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x74, 0x6f, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x54, 0x6f, 0x12, 0x2d, + 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x04, 0x08, + 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, + 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, 0x74, 0x53, 0x61, + 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x41, 0x6d, + 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x53, 0x61, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x6d, 0x74, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x5a, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, - 0x0a, 0x1f, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x89, 0x04, 0x0a, 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x68, 0x61, - 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, - 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x68, 0x6f, - 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, - 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, - 0x65, 0x6c, 0x61, 0x78, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x1b, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x12, 0x46, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x30, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, - 0x6c, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x07, 0x61, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x12, 0x38, 0x0a, 0x07, 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, - 0x42, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x73, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x10, - 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, - 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x10, 0x00, 0x12, 0x0b, 0x0a, - 0x07, 0x42, 0x49, 0x4d, 0x4f, 0x44, 0x41, 0x4c, 0x10, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x45, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x72, 0x0a, - 0x11, 0x42, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x57, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x64, 0x65, 0x63, 0x61, 0x79, 0x54, 0x69, 0x6d, - 0x65, 0x22, 0xad, 0x01, 0x0a, 0x11, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, - 0x6c, 0x69, 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, - 0x6e, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x68, 0x6f, - 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, - 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x77, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, - 0x5f, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x10, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x6a, 0x0a, 0x17, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, - 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, - 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, - 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, - 0x18, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, - 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, - 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, - 0x61, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0xca, 0x01, 0x0a, 0x11, 0x42, - 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, - 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, - 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, - 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, - 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, - 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x22, 0x38, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, - 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, - 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x86, 0x06, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, - 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, - 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, - 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, - 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, - 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, - 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, - 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, - 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, - 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, - 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, - 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x3b, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, - 0x0f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x47, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x45, 0x0a, 0x10, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x3c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, - 0x45, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, - 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, - 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, - 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, - 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, - 0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, - 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, - 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, - 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, - 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, - 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x37, 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, - 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, - 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x29, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, - 0x46, 0x0a, 0x0e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, - 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, - 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x4c, - 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, - 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x77, 0x69, 0x72, 0x65, 0x46, 0x61, 0x69, - 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, - 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, - 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, - 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, - 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, - 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, - 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x22, 0xe9, 0x04, 0x0a, 0x1b, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, - 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, - 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, - 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x69, - 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, - 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, - 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x6f, 0x75, 0x74, - 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, - 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, - 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x60, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, - 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, - 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x75, 0x74, - 0x6f, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x46, 0x61, 0x69, 0x6c, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa8, 0x02, 0x0a, 0x1c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, - 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, - 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, - 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x59, + 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x37, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x74, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x89, 0x04, 0x0a, + 0x14, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, + 0x66, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x68, 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, + 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x02, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x36, + 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, + 0x6d, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x78, 0x5f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1b, + 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, + 0x6c, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x05, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x30, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x62, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x05, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x61, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x12, 0x38, 0x0a, + 0x07, 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x69, 0x6d, 0x6f, 0x64, + 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, + 0x62, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x41, + 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x49, 0x4d, 0x4f, + 0x44, 0x41, 0x4c, 0x10, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, + 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x72, 0x0a, 0x11, 0x42, 0x69, 0x6d, 0x6f, + 0x64, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, + 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x09, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x64, 0x65, 0x63, 0x61, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x09, 0x64, 0x65, 0x63, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xad, 0x01, 0x0a, + 0x11, 0x41, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x6c, 0x66, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x68, + 0x61, 0x6c, 0x66, 0x4c, 0x69, 0x66, 0x65, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x27, + 0x0a, 0x0f, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x68, 0x6f, 0x70, 0x50, 0x72, 0x6f, 0x62, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x2b, 0x0a, 0x11, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x72, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63, 0x61, 0x70, 0x61, + 0x63, 0x69, 0x74, 0x79, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6a, 0x0a, 0x17, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, + 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, + 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x07, 0x61, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x86, 0x03, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, + 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, + 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, + 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, + 0x12, 0x2c, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, + 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x68, 0x6f, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x12, 0x70, 0x0a, 0x18, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x48, 0x0a, 0x1a, 0x46, 0x69, 0x72, 0x73, 0x74, 0x48, 0x6f, 0x70, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x38, + 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x86, 0x06, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x28, + 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4e, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x46, 0x61, + 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x0c, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x66, 0x61, 0x69, + 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, + 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6c, 0x69, 0x6e, 0x6b, 0x46, + 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x45, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, 0x74, 0x6c, + 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x48, + 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x3c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x4f, 0x52, + 0x57, 0x41, 0x52, 0x44, 0x10, 0x03, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0xbc, 0x01, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2b, 0x0a, 0x11, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, + 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x54, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x54, 0x69, + 0x6d, 0x65, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, + 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x6d, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, + 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x22, 0x37, + 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, + 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x12, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x29, 0x0a, 0x0b, 0x53, + 0x65, 0x74, 0x74, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, + 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x46, 0x0a, 0x0e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x48, + 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x22, 0x11, + 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x61, 0x69, 0x6c, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0c, + 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, + 0x77, 0x69, 0x72, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x0d, 0x66, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, + 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x22, 0x8a, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x12, 0x28, 0x0a, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, + 0x6d, 0x70, 0x74, 0x52, 0x05, 0x68, 0x74, 0x6c, 0x63, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, + 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, 0x49, 0x64, + 0x22, 0xa7, 0x06, 0x0a, 0x1b, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x47, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, + 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, + 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, + 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, + 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, + 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x45, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x75, 0x74, 0x67, 0x6f, + 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x6f, 0x75, 0x74, + 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x12, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, + 0x60, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x62, + 0x12, 0x28, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, + 0x46, 0x61, 0x69, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x74, 0x0a, 0x16, 0x69, 0x6e, + 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, + 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x69, 0x6e, 0x57, + 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, + 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x49, 0x6e, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x04, 0x0a, 0x1c, 0x46, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, + 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x14, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, + 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, + 0x74, 0x4b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x6e, 0x5f, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69, + 0x6e, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6f, + 0x75, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, + 0x73, 0x61, 0x74, 0x12, 0x78, 0x0a, 0x17, 0x6f, 0x75, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x75, + 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x6f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x47, 0x0a, + 0x19, 0x4f, 0x75, 0x74, 0x57, 0x69, 0x72, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, + 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, + 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x44, 0x0a, 0x12, + 0x41, 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, + 0x70, 0x73, 0x22, 0x46, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, + 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x73, 0x22, 0x47, 0x0a, 0x15, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6d, 0x61, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4d, + 0x61, 0x70, 0x73, 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, + 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, + 0x45, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, + 0x45, 0x4c, 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, + 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, + 0x12, 0x14, 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, + 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, + 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, + 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, + 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, + 0x5f, 0x41, 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, + 0x11, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, + 0x45, 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, + 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, + 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, + 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, + 0x49, 0x52, 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, + 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, + 0x45, 0x4e, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, + 0x49, 0x43, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, + 0x10, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, + 0x48, 0x10, 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, + 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, + 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, + 0x10, 0x11, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, + 0x49, 0x44, 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, + 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, + 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, + 0x0a, 0x0f, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, + 0x53, 0x10, 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, + 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, + 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, + 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, + 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, + 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, + 0x10, 0x0a, 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x04, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 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, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, + 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x51, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, - 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, - 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x64, 0x65, - 0x22, 0x82, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x0a, - 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x33, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1a, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, - 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2a, 0x81, 0x04, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x44, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, - 0x10, 0x0a, 0x0c, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x10, - 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4c, - 0x49, 0x47, 0x49, 0x42, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x4e, 0x5f, 0x43, - 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x14, - 0x0a, 0x10, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x45, 0x58, 0x43, 0x45, 0x45, 0x44, 0x53, 0x5f, 0x4d, - 0x41, 0x58, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, - 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x16, - 0x0a, 0x12, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, - 0x57, 0x41, 0x52, 0x44, 0x10, 0x07, 0x12, 0x13, 0x0a, 0x0f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, - 0x44, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x08, 0x12, 0x15, 0x0a, 0x11, 0x46, - 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, - 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, - 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x56, 0x4f, - 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, 0x10, 0x0b, 0x12, - 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, - 0x59, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x10, 0x0c, 0x12, 0x14, 0x0a, 0x10, - 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x4e, - 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, - 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x41, - 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x4d, 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, - 0x0f, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x45, 0x54, 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x4d, - 0x49, 0x53, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x10, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x54, - 0x5f, 0x54, 0x4f, 0x54, 0x41, 0x4c, 0x5f, 0x54, 0x4f, 0x4f, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x11, - 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x50, 0x41, 0x49, 0x44, - 0x10, 0x12, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x49, 0x4e, - 0x56, 0x4f, 0x49, 0x43, 0x45, 0x10, 0x13, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, 0x41, 0x4c, - 0x49, 0x44, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x14, 0x12, 0x13, 0x0a, 0x0f, - 0x4d, 0x50, 0x50, 0x5f, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, - 0x15, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x49, 0x52, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x5f, 0x52, 0x4f, - 0x55, 0x54, 0x45, 0x10, 0x16, 0x2a, 0xae, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, - 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, - 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x46, 0x41, 0x49, 0x4c, - 0x45, 0x44, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, - 0x0c, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, - 0x24, 0x0a, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 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, 0x05, 0x12, 0x1f, 0x0a, 0x1b, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, - 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, - 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x3c, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, - 0x65, 0x48, 0x6f, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x08, - 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x53, 0x55, - 0x4d, 0x45, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, - 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, - 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, 0x10, 0x02, 0x32, 0xb5, 0x0c, 0x0a, 0x06, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, - 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, - 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, - 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, - 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, - 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, - 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, - 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x10, 0x00, + 0x12, 0x08, 0x0a, 0x04, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, + 0x53, 0x55, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x55, 0x4d, 0x45, + 0x5f, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x10, 0x43, + 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x0a, 0x0a, 0x06, 0x45, 0x4e, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, + 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x55, 0x54, 0x4f, + 0x10, 0x02, 0x32, 0xe8, 0x0d, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x40, 0x0a, + 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x32, 0x12, 0x1d, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, + 0x42, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x56, + 0x32, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, + 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x69, 0x6d, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x1a, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x54, + 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x48, 0x54, 0x4c, 0x43, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x12, 0x64, 0x0a, 0x13, 0x52, + 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x64, 0x0a, 0x13, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x58, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x58, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, - 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, - 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, - 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, - 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, - 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, - 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, - 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, - 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, - 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x29, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5b, 0x0a, 0x10, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x22, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x50, 0x72, 0x6f, + 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x50, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x54, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x48, 0x74, 0x6c, 0x63, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, 0x88, + 0x02, 0x01, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x03, + 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x66, 0x0a, 0x0f, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x27, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x26, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x48, 0x74, - 0x6c, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 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, 0x2f, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5b, 0x0a, + 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x22, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, + 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x14, 0x58, 0x41, + 0x64, 0x64, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, + 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x5c, 0x0a, 0x17, 0x58, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x43, + 0x68, 0x61, 0x6e, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, + 0x2f, 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, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3940,7 +4314,7 @@ func file_routerrpc_router_proto_rawDescGZIP() []byte { } var file_routerrpc_router_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 43) +var file_routerrpc_router_proto_msgTypes = make([]protoimpl.MessageInfo, 52) var file_routerrpc_router_proto_goTypes = []interface{}{ (FailureDetail)(0), // 0: routerrpc.FailureDetail (PaymentState)(0), // 1: routerrpc.PaymentState @@ -3989,96 +4363,119 @@ var file_routerrpc_router_proto_goTypes = []interface{}{ (*ForwardHtlcInterceptResponse)(nil), // 44: routerrpc.ForwardHtlcInterceptResponse (*UpdateChanStatusRequest)(nil), // 45: routerrpc.UpdateChanStatusRequest (*UpdateChanStatusResponse)(nil), // 46: routerrpc.UpdateChanStatusResponse - nil, // 47: routerrpc.SendPaymentRequest.DestCustomRecordsEntry - nil, // 48: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - (*lnrpc.RouteHint)(nil), // 49: lnrpc.RouteHint - (lnrpc.FeatureBit)(0), // 50: lnrpc.FeatureBit - (lnrpc.PaymentFailureReason)(0), // 51: lnrpc.PaymentFailureReason - (*lnrpc.Route)(nil), // 52: lnrpc.Route - (*lnrpc.Failure)(nil), // 53: lnrpc.Failure - (lnrpc.Failure_FailureCode)(0), // 54: lnrpc.Failure.FailureCode - (*lnrpc.HTLCAttempt)(nil), // 55: lnrpc.HTLCAttempt - (*lnrpc.ChannelPoint)(nil), // 56: lnrpc.ChannelPoint - (*lnrpc.Payment)(nil), // 57: lnrpc.Payment + (*AddAliasesRequest)(nil), // 47: routerrpc.AddAliasesRequest + (*AddAliasesResponse)(nil), // 48: routerrpc.AddAliasesResponse + (*DeleteAliasesRequest)(nil), // 49: routerrpc.DeleteAliasesRequest + (*DeleteAliasesResponse)(nil), // 50: routerrpc.DeleteAliasesResponse + nil, // 51: routerrpc.SendPaymentRequest.DestCustomRecordsEntry + nil, // 52: routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry + nil, // 53: routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry + nil, // 54: routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry + nil, // 55: routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + nil, // 56: routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry + nil, // 57: routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry + (*lnrpc.RouteHint)(nil), // 58: lnrpc.RouteHint + (lnrpc.FeatureBit)(0), // 59: lnrpc.FeatureBit + (lnrpc.PaymentFailureReason)(0), // 60: lnrpc.PaymentFailureReason + (*lnrpc.Route)(nil), // 61: lnrpc.Route + (*lnrpc.Failure)(nil), // 62: lnrpc.Failure + (lnrpc.Failure_FailureCode)(0), // 63: lnrpc.Failure.FailureCode + (*lnrpc.HTLCAttempt)(nil), // 64: lnrpc.HTLCAttempt + (*lnrpc.ChannelPoint)(nil), // 65: lnrpc.ChannelPoint + (*lnrpc.AliasMap)(nil), // 66: lnrpc.AliasMap + (*lnrpc.Payment)(nil), // 67: lnrpc.Payment } var file_routerrpc_router_proto_depIdxs = []int32{ - 49, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint - 47, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry - 50, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit - 51, // 3: routerrpc.RouteFeeResponse.failure_reason:type_name -> lnrpc.PaymentFailureReason - 52, // 4: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route - 53, // 5: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure - 19, // 6: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory - 19, // 7: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory - 20, // 8: routerrpc.PairHistory.history:type_name -> routerrpc.PairData - 25, // 9: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig - 25, // 10: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig - 4, // 11: routerrpc.MissionControlConfig.model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel - 27, // 12: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters - 26, // 13: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters - 20, // 14: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData - 52, // 15: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route - 5, // 16: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType - 35, // 17: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent - 36, // 18: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent - 37, // 19: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent - 40, // 20: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent - 39, // 21: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent - 38, // 22: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent - 34, // 23: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo - 34, // 24: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo - 54, // 25: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode - 0, // 26: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail - 1, // 27: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState - 55, // 28: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt - 42, // 29: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 48, // 30: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry - 42, // 31: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey - 2, // 32: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction - 54, // 33: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode - 56, // 34: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint - 3, // 35: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction - 6, // 36: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest - 7, // 37: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest - 8, // 38: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest - 9, // 39: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest - 11, // 40: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest - 11, // 41: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest - 13, // 42: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest - 15, // 43: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest - 17, // 44: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest - 21, // 45: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest - 23, // 46: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest - 28, // 47: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest - 30, // 48: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest - 32, // 49: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest - 6, // 50: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest - 7, // 51: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest - 44, // 52: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse - 45, // 53: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest - 57, // 54: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment - 57, // 55: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment - 57, // 56: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment - 10, // 57: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse - 12, // 58: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse - 55, // 59: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt - 14, // 60: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse - 16, // 61: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse - 18, // 62: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse - 22, // 63: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse - 24, // 64: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse - 29, // 65: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse - 31, // 66: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse - 33, // 67: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent - 41, // 68: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus - 41, // 69: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus - 43, // 70: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest - 46, // 71: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse - 54, // [54:72] is the sub-list for method output_type - 36, // [36:54] is the sub-list for method input_type - 36, // [36:36] is the sub-list for extension type_name - 36, // [36:36] is the sub-list for extension extendee - 0, // [0:36] is the sub-list for field type_name + 58, // 0: routerrpc.SendPaymentRequest.route_hints:type_name -> lnrpc.RouteHint + 51, // 1: routerrpc.SendPaymentRequest.dest_custom_records:type_name -> routerrpc.SendPaymentRequest.DestCustomRecordsEntry + 59, // 2: routerrpc.SendPaymentRequest.dest_features:type_name -> lnrpc.FeatureBit + 52, // 3: routerrpc.SendPaymentRequest.first_hop_custom_records:type_name -> routerrpc.SendPaymentRequest.FirstHopCustomRecordsEntry + 60, // 4: routerrpc.RouteFeeResponse.failure_reason:type_name -> lnrpc.PaymentFailureReason + 61, // 5: routerrpc.SendToRouteRequest.route:type_name -> lnrpc.Route + 53, // 6: routerrpc.SendToRouteRequest.first_hop_custom_records:type_name -> routerrpc.SendToRouteRequest.FirstHopCustomRecordsEntry + 62, // 7: routerrpc.SendToRouteResponse.failure:type_name -> lnrpc.Failure + 19, // 8: routerrpc.QueryMissionControlResponse.pairs:type_name -> routerrpc.PairHistory + 19, // 9: routerrpc.XImportMissionControlRequest.pairs:type_name -> routerrpc.PairHistory + 20, // 10: routerrpc.PairHistory.history:type_name -> routerrpc.PairData + 25, // 11: routerrpc.GetMissionControlConfigResponse.config:type_name -> routerrpc.MissionControlConfig + 25, // 12: routerrpc.SetMissionControlConfigRequest.config:type_name -> routerrpc.MissionControlConfig + 4, // 13: routerrpc.MissionControlConfig.model:type_name -> routerrpc.MissionControlConfig.ProbabilityModel + 27, // 14: routerrpc.MissionControlConfig.apriori:type_name -> routerrpc.AprioriParameters + 26, // 15: routerrpc.MissionControlConfig.bimodal:type_name -> routerrpc.BimodalParameters + 20, // 16: routerrpc.QueryProbabilityResponse.history:type_name -> routerrpc.PairData + 54, // 17: routerrpc.BuildRouteRequest.first_hop_custom_records:type_name -> routerrpc.BuildRouteRequest.FirstHopCustomRecordsEntry + 61, // 18: routerrpc.BuildRouteResponse.route:type_name -> lnrpc.Route + 5, // 19: routerrpc.HtlcEvent.event_type:type_name -> routerrpc.HtlcEvent.EventType + 35, // 20: routerrpc.HtlcEvent.forward_event:type_name -> routerrpc.ForwardEvent + 36, // 21: routerrpc.HtlcEvent.forward_fail_event:type_name -> routerrpc.ForwardFailEvent + 37, // 22: routerrpc.HtlcEvent.settle_event:type_name -> routerrpc.SettleEvent + 40, // 23: routerrpc.HtlcEvent.link_fail_event:type_name -> routerrpc.LinkFailEvent + 39, // 24: routerrpc.HtlcEvent.subscribed_event:type_name -> routerrpc.SubscribedEvent + 38, // 25: routerrpc.HtlcEvent.final_htlc_event:type_name -> routerrpc.FinalHtlcEvent + 34, // 26: routerrpc.ForwardEvent.info:type_name -> routerrpc.HtlcInfo + 34, // 27: routerrpc.LinkFailEvent.info:type_name -> routerrpc.HtlcInfo + 63, // 28: routerrpc.LinkFailEvent.wire_failure:type_name -> lnrpc.Failure.FailureCode + 0, // 29: routerrpc.LinkFailEvent.failure_detail:type_name -> routerrpc.FailureDetail + 1, // 30: routerrpc.PaymentStatus.state:type_name -> routerrpc.PaymentState + 64, // 31: routerrpc.PaymentStatus.htlcs:type_name -> lnrpc.HTLCAttempt + 42, // 32: routerrpc.ForwardHtlcInterceptRequest.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 55, // 33: routerrpc.ForwardHtlcInterceptRequest.custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry + 56, // 34: routerrpc.ForwardHtlcInterceptRequest.in_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptRequest.InWireCustomRecordsEntry + 42, // 35: routerrpc.ForwardHtlcInterceptResponse.incoming_circuit_key:type_name -> routerrpc.CircuitKey + 2, // 36: routerrpc.ForwardHtlcInterceptResponse.action:type_name -> routerrpc.ResolveHoldForwardAction + 63, // 37: routerrpc.ForwardHtlcInterceptResponse.failure_code:type_name -> lnrpc.Failure.FailureCode + 57, // 38: routerrpc.ForwardHtlcInterceptResponse.out_wire_custom_records:type_name -> routerrpc.ForwardHtlcInterceptResponse.OutWireCustomRecordsEntry + 65, // 39: routerrpc.UpdateChanStatusRequest.chan_point:type_name -> lnrpc.ChannelPoint + 3, // 40: routerrpc.UpdateChanStatusRequest.action:type_name -> routerrpc.ChanStatusAction + 66, // 41: routerrpc.AddAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap + 66, // 42: routerrpc.AddAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap + 66, // 43: routerrpc.DeleteAliasesRequest.alias_maps:type_name -> lnrpc.AliasMap + 66, // 44: routerrpc.DeleteAliasesResponse.alias_maps:type_name -> lnrpc.AliasMap + 6, // 45: routerrpc.Router.SendPaymentV2:input_type -> routerrpc.SendPaymentRequest + 7, // 46: routerrpc.Router.TrackPaymentV2:input_type -> routerrpc.TrackPaymentRequest + 8, // 47: routerrpc.Router.TrackPayments:input_type -> routerrpc.TrackPaymentsRequest + 9, // 48: routerrpc.Router.EstimateRouteFee:input_type -> routerrpc.RouteFeeRequest + 11, // 49: routerrpc.Router.SendToRoute:input_type -> routerrpc.SendToRouteRequest + 11, // 50: routerrpc.Router.SendToRouteV2:input_type -> routerrpc.SendToRouteRequest + 13, // 51: routerrpc.Router.ResetMissionControl:input_type -> routerrpc.ResetMissionControlRequest + 15, // 52: routerrpc.Router.QueryMissionControl:input_type -> routerrpc.QueryMissionControlRequest + 17, // 53: routerrpc.Router.XImportMissionControl:input_type -> routerrpc.XImportMissionControlRequest + 21, // 54: routerrpc.Router.GetMissionControlConfig:input_type -> routerrpc.GetMissionControlConfigRequest + 23, // 55: routerrpc.Router.SetMissionControlConfig:input_type -> routerrpc.SetMissionControlConfigRequest + 28, // 56: routerrpc.Router.QueryProbability:input_type -> routerrpc.QueryProbabilityRequest + 30, // 57: routerrpc.Router.BuildRoute:input_type -> routerrpc.BuildRouteRequest + 32, // 58: routerrpc.Router.SubscribeHtlcEvents:input_type -> routerrpc.SubscribeHtlcEventsRequest + 6, // 59: routerrpc.Router.SendPayment:input_type -> routerrpc.SendPaymentRequest + 7, // 60: routerrpc.Router.TrackPayment:input_type -> routerrpc.TrackPaymentRequest + 44, // 61: routerrpc.Router.HtlcInterceptor:input_type -> routerrpc.ForwardHtlcInterceptResponse + 45, // 62: routerrpc.Router.UpdateChanStatus:input_type -> routerrpc.UpdateChanStatusRequest + 47, // 63: routerrpc.Router.XAddLocalChanAliases:input_type -> routerrpc.AddAliasesRequest + 49, // 64: routerrpc.Router.XDeleteLocalChanAliases:input_type -> routerrpc.DeleteAliasesRequest + 67, // 65: routerrpc.Router.SendPaymentV2:output_type -> lnrpc.Payment + 67, // 66: routerrpc.Router.TrackPaymentV2:output_type -> lnrpc.Payment + 67, // 67: routerrpc.Router.TrackPayments:output_type -> lnrpc.Payment + 10, // 68: routerrpc.Router.EstimateRouteFee:output_type -> routerrpc.RouteFeeResponse + 12, // 69: routerrpc.Router.SendToRoute:output_type -> routerrpc.SendToRouteResponse + 64, // 70: routerrpc.Router.SendToRouteV2:output_type -> lnrpc.HTLCAttempt + 14, // 71: routerrpc.Router.ResetMissionControl:output_type -> routerrpc.ResetMissionControlResponse + 16, // 72: routerrpc.Router.QueryMissionControl:output_type -> routerrpc.QueryMissionControlResponse + 18, // 73: routerrpc.Router.XImportMissionControl:output_type -> routerrpc.XImportMissionControlResponse + 22, // 74: routerrpc.Router.GetMissionControlConfig:output_type -> routerrpc.GetMissionControlConfigResponse + 24, // 75: routerrpc.Router.SetMissionControlConfig:output_type -> routerrpc.SetMissionControlConfigResponse + 29, // 76: routerrpc.Router.QueryProbability:output_type -> routerrpc.QueryProbabilityResponse + 31, // 77: routerrpc.Router.BuildRoute:output_type -> routerrpc.BuildRouteResponse + 33, // 78: routerrpc.Router.SubscribeHtlcEvents:output_type -> routerrpc.HtlcEvent + 41, // 79: routerrpc.Router.SendPayment:output_type -> routerrpc.PaymentStatus + 41, // 80: routerrpc.Router.TrackPayment:output_type -> routerrpc.PaymentStatus + 43, // 81: routerrpc.Router.HtlcInterceptor:output_type -> routerrpc.ForwardHtlcInterceptRequest + 46, // 82: routerrpc.Router.UpdateChanStatus:output_type -> routerrpc.UpdateChanStatusResponse + 48, // 83: routerrpc.Router.XAddLocalChanAliases:output_type -> routerrpc.AddAliasesResponse + 50, // 84: routerrpc.Router.XDeleteLocalChanAliases:output_type -> routerrpc.DeleteAliasesResponse + 65, // [65:85] is the sub-list for method output_type + 45, // [45:65] is the sub-list for method input_type + 45, // [45:45] is the sub-list for extension type_name + 45, // [45:45] is the sub-list for extension extendee + 0, // [0:45] is the sub-list for field type_name } func init() { file_routerrpc_router_proto_init() } @@ -4579,6 +4976,54 @@ func file_routerrpc_router_proto_init() { return nil } } + file_routerrpc_router_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAliasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAliasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAliasesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routerrpc_router_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAliasesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_routerrpc_router_proto_msgTypes[19].OneofWrappers = []interface{}{ (*MissionControlConfig_Apriori)(nil), @@ -4598,7 +5043,7 @@ func file_routerrpc_router_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routerrpc_router_proto_rawDesc, NumEnums: 6, - NumMessages: 43, + NumMessages: 52, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/routerrpc/router.pb.gw.go b/lnrpc/routerrpc/router.pb.gw.go index fcfc3ad82e..be2498bf5c 100644 --- a/lnrpc/routerrpc/router.pb.gw.go +++ b/lnrpc/routerrpc/router.pb.gw.go @@ -564,6 +564,74 @@ func local_request_Router_UpdateChanStatus_0(ctx context.Context, marshaler runt } +func request_Router_XAddLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddAliasesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.XAddLocalChanAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Router_XAddLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddAliasesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.XAddLocalChanAliases(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Router_XDeleteLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, client RouterClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAliasesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.XDeleteLocalChanAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Router_XDeleteLocalChanAliases_0(ctx context.Context, marshaler runtime.Marshaler, server RouterServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAliasesRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.XDeleteLocalChanAliases(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterRouterHandlerServer registers the http handlers for service Router to "mux". // UnaryRPC :call RouterServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -835,6 +903,52 @@ func RegisterRouterHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser }) + mux.Handle("POST", pattern_Router_XAddLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/XAddLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/addaliases")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Router_XAddLocalChanAliases_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Router_XAddLocalChanAliases_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Router_XDeleteLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/routerrpc.Router/XDeleteLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/deletealiases")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Router_XDeleteLocalChanAliases_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Router_XDeleteLocalChanAliases_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1176,6 +1290,46 @@ func RegisterRouterHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli }) + mux.Handle("POST", pattern_Router_XAddLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/XAddLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/addaliases")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Router_XAddLocalChanAliases_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Router_XAddLocalChanAliases_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Router_XDeleteLocalChanAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/routerrpc.Router/XDeleteLocalChanAliases", runtime.WithHTTPPathPattern("/v2/router/x/deletealiases")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Router_XDeleteLocalChanAliases_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Router_XDeleteLocalChanAliases_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1209,6 +1363,10 @@ var ( pattern_Router_HtlcInterceptor_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "htlcinterceptor"}, "")) pattern_Router_UpdateChanStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "router", "updatechanstatus"}, "")) + + pattern_Router_XAddLocalChanAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "addaliases"}, "")) + + pattern_Router_XDeleteLocalChanAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "router", "x", "deletealiases"}, "")) ) var ( @@ -1241,4 +1399,8 @@ var ( forward_Router_HtlcInterceptor_0 = runtime.ForwardResponseStream forward_Router_UpdateChanStatus_0 = runtime.ForwardResponseMessage + + forward_Router_XAddLocalChanAliases_0 = runtime.ForwardResponseMessage + + forward_Router_XDeleteLocalChanAliases_0 = runtime.ForwardResponseMessage ) diff --git a/lnrpc/routerrpc/router.pb.json.go b/lnrpc/routerrpc/router.pb.json.go index 03b9e8a24e..1b87402942 100644 --- a/lnrpc/routerrpc/router.pb.json.go +++ b/lnrpc/routerrpc/router.pb.json.go @@ -547,4 +547,54 @@ func RegisterRouterJSONCallbacks(registry map[string]func(ctx context.Context, } callback(string(respBytes), nil) } + + registry["routerrpc.Router.XAddLocalChanAliases"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &AddAliasesRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewRouterClient(conn) + resp, err := client.XAddLocalChanAliases(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["routerrpc.Router.XDeleteLocalChanAliases"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &DeleteAliasesRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewRouterClient(conn) + resp, err := client.XDeleteLocalChanAliases(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 1856bccbee..92da751929 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -176,6 +176,25 @@ service Router { */ rpc UpdateChanStatus (UpdateChanStatusRequest) returns (UpdateChanStatusResponse); + + /* + XAddLocalChanAliases is an experimental API that creates a set of new + channel SCID alias mappings. The final total set of aliases in the manager + after the add operation is returned. This is only a locally stored alias, + and will not be communicated to the channel peer via any message. Therefore, + routing over such an alias will only work if the peer also calls this same + RPC on their end. If an alias already exists, an error is returned + */ + rpc XAddLocalChanAliases (AddAliasesRequest) returns (AddAliasesResponse); + + /* + XDeleteLocalChanAliases is an experimental API that deletes a set of alias + mappings. The final total set of aliases in the manager after the delete + operation is returned. The deletion will not be communicated to the channel + peer via any message. + */ + rpc XDeleteLocalChanAliases (DeleteAliasesRequest) + returns (DeleteAliasesResponse); } message SendPaymentRequest { @@ -339,6 +358,15 @@ message SendPaymentRequest { being sent. */ bool cancelable = 24; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to the first hop peer of this payment. This can be used to pass application + specific data during the payment attempt. Record types are required to be in + the custom range >= 65536. When using REST, the values must be encoded as + base64. + */ + map first_hop_custom_records = 25; } message TrackPaymentRequest { @@ -432,6 +460,15 @@ message SendToRouteRequest { routes, incorrect payment details, or insufficient funds. */ bool skip_temp_err = 3; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to the first hop peer of this payment. This can be used to pass application + specific data during the payment attempt. Record types are required to be in + the custom range >= 65536. When using REST, the values must be encoded as + base64. + */ + map first_hop_custom_records = 4; } message SendToRouteResponse { @@ -707,6 +744,15 @@ message BuildRouteRequest { This is also called payment secret in specifications (e.g. BOLT 11). */ bytes payment_addr = 5; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to the first hop peer of this payment. This can be used to pass application + specific data during the payment attempt. Record types are required to be in + the custom range >= 65536. When using REST, the values must be encoded as + base64. + */ + map first_hop_custom_records = 6; } message BuildRouteResponse { @@ -963,12 +1009,17 @@ message ForwardHtlcInterceptRequest { // The block height at which this htlc will be auto-failed to prevent the // channel from force-closing. int32 auto_fail_height = 10; + + // The custom records of the peer's incoming p2p wire message. + map in_wire_custom_records = 11; } /** ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward. The caller can choose either to: - `Resume`: Execute the default behavior (usually forward). +- `ResumeModified`: Execute the default behavior (usually forward) with HTLC + field modifications. - `Reject`: Fail the htlc backwards. - `Settle`: Settle this htlc with a given preimage. */ @@ -999,12 +1050,36 @@ message ForwardHtlcInterceptResponse { // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the // default value for this field. lnrpc.Failure.FailureCode failure_code = 5; + + // The amount that was set on the p2p wire message of the incoming HTLC. + // This field is ignored if the action is not RESUME_MODIFIED or the amount + // is zero. + uint64 in_amount_msat = 6; + + // The amount to set on the p2p wire message of the resumed HTLC. This field + // is ignored if the action is not RESUME_MODIFIED or the amount is zero. + uint64 out_amount_msat = 7; + + // Any custom records that should be set on the p2p wire message message of + // the resumed HTLC. This field is ignored if the action is not + // RESUME_MODIFIED. + map out_wire_custom_records = 8; } enum ResolveHoldForwardAction { + // SETTLE is an action that is used to settle an HTLC instead of forwarding + // it. SETTLE = 0; + + // FAIL is an action that is used to fail an HTLC backwards. FAIL = 1; + + // RESUME is an action that is used to resume a forward HTLC. RESUME = 2; + + // RESUME_MODIFIED is an action that is used to resume a hold forward HTLC + // with modifications specified during interception. + RESUME_MODIFIED = 3; } message UpdateChanStatusRequest { @@ -1021,3 +1096,19 @@ enum ChanStatusAction { message UpdateChanStatusResponse { } + +message AddAliasesRequest { + repeated lnrpc.AliasMap alias_maps = 1; +} + +message AddAliasesResponse { + repeated lnrpc.AliasMap alias_maps = 1; +} + +message DeleteAliasesRequest { + repeated lnrpc.AliasMap alias_maps = 1; +} + +message DeleteAliasesResponse { + repeated lnrpc.AliasMap alias_maps = 1; +} \ No newline at end of file diff --git a/lnrpc/routerrpc/router.swagger.json b/lnrpc/routerrpc/router.swagger.json index 7c8aa6217d..b838f475d1 100644 --- a/lnrpc/routerrpc/router.swagger.json +++ b/lnrpc/routerrpc/router.swagger.json @@ -514,6 +514,72 @@ ] } }, + "/v2/router/x/addaliases": { + "post": { + "summary": "XAddLocalChanAliases is an experimental API that creates a set of new\nchannel SCID alias mappings. The final total set of aliases in the manager\nafter the add operation is returned. This is only a locally stored alias,\nand will not be communicated to the channel peer via any message. Therefore,\nrouting over such an alias will only work if the peer also calls this same\nRPC on their end. If an alias already exists, an error is returned", + "operationId": "Router_XAddLocalChanAliases", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/routerrpcAddAliasesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/routerrpcAddAliasesRequest" + } + } + ], + "tags": [ + "Router" + ] + } + }, + "/v2/router/x/deletealiases": { + "post": { + "summary": "XDeleteLocalChanAliases is an experimental API that deletes a set of alias\nmappings. The final total set of aliases in the manager after the delete\noperation is returned. The deletion will not be communicated to the channel\npeer via any message.", + "operationId": "Router_XDeleteLocalChanAliases", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/routerrpcDeleteAliasesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/routerrpcDeleteAliasesRequest" + } + } + ], + "tags": [ + "Router" + ] + } + }, "/v2/router/x/importhistory": { "post": { "summary": "lncli: `importmc`\nXImportMissionControl is an experimental API that imports the state provided\nto the internal mission control's state, using all results which are more\nrecent than our existing values. These values will only be imported\nin-memory, and will not be persisted across restarts.", @@ -619,6 +685,24 @@ } } }, + "lnrpcAliasMap": { + "type": "object", + "properties": { + "base_scid": { + "type": "string", + "format": "uint64", + "description": "For non-zero-conf channels, this is the confirmed SCID. Otherwise, this is\nthe first assigned \"base\" alias." + }, + "aliases": { + "type": "array", + "items": { + "type": "string", + "format": "uint64" + }, + "description": "The set of all aliases stored for the base SCID." + } + } + }, "lnrpcChannelPoint": { "type": "object", "properties": { @@ -1011,6 +1095,14 @@ }, "failure_reason": { "$ref": "#/definitions/lnrpcPaymentFailureReason" + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "The custom TLV records that were sent to the first hop as part of the HTLC\nwire message for this payment." } } }, @@ -1074,6 +1166,16 @@ "type": "string", "format": "int64", "description": "The total amount in millisatoshis." + }, + "first_hop_amount_msat": { + "type": "string", + "format": "int64", + "description": "The actual on-chain amount that was sent out to the first hop. This value is\nonly different from the total_amt_msat field if this is a custom channel\npayment and the value transported in the HTLC is different from the BTC\namount in the HTLC. If this value is zero, then this is an old payment that\ndidn't have this value yet and can be ignored." + }, + "custom_channel_data": { + "type": "string", + "format": "byte", + "description": "Custom channel data that might be populated in custom channels." } }, "description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for." @@ -1102,6 +1204,28 @@ } } }, + "routerrpcAddAliasesRequest": { + "type": "object", + "properties": { + "alias_maps": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcAliasMap" + } + } + } + }, + "routerrpcAddAliasesResponse": { + "type": "object", + "properties": { + "alias_maps": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcAliasMap" + } + } + } + }, "routerrpcAprioriParameters": { "type": "object", "properties": { @@ -1177,6 +1301,14 @@ "type": "string", "format": "byte", "description": "An optional payment addr to be included within the last hop of the route.\nThis is also called payment secret in specifications (e.g. BOLT 11)." + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64." } } }, @@ -1213,6 +1345,28 @@ } } }, + "routerrpcDeleteAliasesRequest": { + "type": "object", + "properties": { + "alias_maps": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcAliasMap" + } + } + } + }, + "routerrpcDeleteAliasesResponse": { + "type": "object", + "properties": { + "alias_maps": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcAliasMap" + } + } + } + }, "routerrpcFailureDetail": { "type": "string", "enum": [ @@ -1319,6 +1473,14 @@ "type": "integer", "format": "int32", "description": "The block height at which this htlc will be auto-failed to prevent the\nchannel from force-closing." + }, + "in_wire_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "The custom records of the peer's incoming p2p wire message." } } }, @@ -1346,9 +1508,27 @@ "failure_code": { "$ref": "#/definitions/FailureFailureCode", "description": "Return the specified failure code in case the resolve action is Fail. The\nmessage data fields are populated automatically.\n\nIf a non-zero failure_code is specified, failure_message must not be set.\n\nFor backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the\ndefault value for this field." + }, + "in_amount_msat": { + "type": "string", + "format": "uint64", + "description": "The amount that was set on the p2p wire message of the incoming HTLC.\nThis field is ignored if the action is not RESUME_MODIFIED or the amount\nis zero." + }, + "out_amount_msat": { + "type": "string", + "format": "uint64", + "description": "The amount to set on the p2p wire message of the resumed HTLC. This field\nis ignored if the action is not RESUME_MODIFIED or the amount is zero." + }, + "out_wire_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "Any custom records that should be set on the p2p wire message message of\nthe resumed HTLC. This field is ignored if the action is not\nRESUME_MODIFIED." } }, - "description": "*\nForwardHtlcInterceptResponse enables the caller to resolve a previously hold\nforward. The caller can choose either to:\n- `Resume`: Execute the default behavior (usually forward).\n- `Reject`: Fail the htlc backwards.\n- `Settle`: Settle this htlc with a given preimage." + "description": "*\nForwardHtlcInterceptResponse enables the caller to resolve a previously hold\nforward. The caller can choose either to:\n- `Resume`: Execute the default behavior (usually forward).\n- `ResumeModified`: Execute the default behavior (usually forward) with HTLC\nfield modifications.\n- `Reject`: Fail the htlc backwards.\n- `Settle`: Settle this htlc with a given preimage." }, "routerrpcGetMissionControlConfigResponse": { "type": "object", @@ -1635,9 +1815,11 @@ "enum": [ "SETTLE", "FAIL", - "RESUME" + "RESUME", + "RESUME_MODIFIED" ], - "default": "SETTLE" + "default": "SETTLE", + "description": " - SETTLE: SETTLE is an action that is used to settle an HTLC instead of forwarding\nit.\n - FAIL: FAIL is an action that is used to fail an HTLC backwards.\n - RESUME: RESUME is an action that is used to resume a forward HTLC.\n - RESUME_MODIFIED: RESUME_MODIFIED is an action that is used to resume a hold forward HTLC\nwith modifications specified during interception." }, "routerrpcRouteFeeRequest": { "type": "object", @@ -1809,6 +1991,14 @@ "cancelable": { "type": "boolean", "description": "If set, the payment loop can be interrupted by manually canceling the\npayment context, even before the payment timeout is reached. Note that the\npayment may still succeed after cancellation, as in-flight attempts can\nstill settle afterwards. Canceling will only prevent further attempts from\nbeing sent." + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64." } } }, @@ -1827,6 +2017,14 @@ "skip_temp_err": { "type": "boolean", "description": "Whether the payment should be marked as failed when a temporary error is\nreturned from the given route. Set it to true so the payment won't be\nfailed unless a terminal error is occurred, such as payment timeout, no\nroutes, incorrect payment details, or insufficient funds." + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64." } } }, diff --git a/lnrpc/routerrpc/router.yaml b/lnrpc/routerrpc/router.yaml index 7c69376edf..4d1d370ffb 100644 --- a/lnrpc/routerrpc/router.yaml +++ b/lnrpc/routerrpc/router.yaml @@ -48,3 +48,10 @@ http: - selector: routerrpc.Router.UpdateChanStatus post: "/v2/router/updatechanstatus" body: "*" + - selector: routerrpc.Router.XAddLocalChanAliases + post: "/v2/router/x/addaliases" + body: "*" + - selector: routerrpc.Router.XDeleteLocalChanAliases + post: "/v2/router/x/deletealiases" + body: "*" + diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index ac493ba330..7aa7e5c098 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -16,6 +16,7 @@ import ( sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/feature" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -25,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" "github.com/lightningnetwork/lnd/zpay32" + "google.golang.org/protobuf/proto" ) const ( @@ -104,6 +106,10 @@ type RouterBackend struct { // TODO(yy): remove this config after the new status code is fully // deployed to the network(v0.20.0). UseStatusInitiated bool + + // ParseCustomChannelData is a function that can be used to parse custom + // channel data from the first hop of a route. + ParseCustomChannelData func(message proto.Message) error } // MissionControl defines the mission control dependencies of routerrpc. @@ -578,13 +584,34 @@ func (r *RouterBackend) rpcEdgeToPair(e *lnrpc.EdgeLocator) ( // MarshallRoute marshalls an internal route to an rpc route struct. func (r *RouterBackend) MarshallRoute(route *route.Route) (*lnrpc.Route, error) { resp := &lnrpc.Route{ - TotalTimeLock: route.TotalTimeLock, - TotalFees: int64(route.TotalFees().ToSatoshis()), - TotalFeesMsat: int64(route.TotalFees()), - TotalAmt: int64(route.TotalAmount.ToSatoshis()), - TotalAmtMsat: int64(route.TotalAmount), - Hops: make([]*lnrpc.Hop, len(route.Hops)), + TotalTimeLock: route.TotalTimeLock, + TotalFees: int64(route.TotalFees().ToSatoshis()), + TotalFeesMsat: int64(route.TotalFees()), + TotalAmt: int64(route.TotalAmount.ToSatoshis()), + TotalAmtMsat: int64(route.TotalAmount), + Hops: make([]*lnrpc.Hop, len(route.Hops)), + FirstHopAmountMsat: int64(route.FirstHopAmount.Val.Int()), + } + + // Encode the route's custom channel data (if available). + if len(route.FirstHopWireCustomRecords) > 0 { + customData, err := route.FirstHopWireCustomRecords.Serialize() + if err != nil { + return nil, err + } + + resp.CustomChannelData = customData + + // Allow the aux data parser to parse the custom records into + // a human-readable JSON (if available). + if r.ParseCustomChannelData != nil { + err := r.ParseCustomChannelData(resp) + if err != nil { + return nil, err + } + } } + incomingAmt := route.TotalAmount for i, hop := range route.Hops { fee := route.HopFee(i) @@ -858,6 +885,12 @@ func (r *RouterBackend) extractIntentFromSendRequest( } payIntent.DestCustomRecords = customRecords + firstHopRecords := lnwire.CustomRecords(rpcPayReq.FirstHopCustomRecords) + if err := firstHopRecords.Validate(); err != nil { + return nil, err + } + payIntent.FirstHopCustomRecords = firstHopRecords + payIntent.PayAttemptTimeout = time.Second * time.Duration(rpcPayReq.TimeoutSeconds) @@ -979,7 +1012,7 @@ func (r *RouterBackend) extractIntentFromSendRequest( if len(rpcPayReq.PaymentAddr) > 0 { var addr [32]byte copy(addr[:], rpcPayReq.PaymentAddr) - payAddr = &addr + payAddr = fn.Some(addr) } } else { err = payIntent.SetPaymentHash(*payReq.PaymentHash) @@ -1096,7 +1129,7 @@ func (r *RouterBackend) extractIntentFromSendRequest( } else { copy(payAddr[:], rpcPayReq.PaymentAddr) } - payIntent.PaymentAddr = &payAddr + payIntent.PaymentAddr = fn.Some(payAddr) // Generate random SetID and root share. var setID [32]byte @@ -1135,7 +1168,7 @@ func (r *RouterBackend) extractIntentFromSendRequest( var payAddr [32]byte copy(payAddr[:], rpcPayReq.PaymentAddr) - payIntent.PaymentAddr = &payAddr + payIntent.PaymentAddr = fn.Some(payAddr) } } @@ -1676,21 +1709,22 @@ func (r *RouterBackend) MarshallPayment(payment *channeldb.MPPayment) ( return &lnrpc.Payment{ // TODO: set this to setID for AMP-payments? - PaymentHash: hex.EncodeToString(paymentID[:]), - Value: satValue, - ValueMsat: msatValue, - ValueSat: satValue, - CreationDate: payment.Info.CreationTime.Unix(), - CreationTimeNs: creationTimeNS, - Fee: int64(fee.ToSatoshis()), - FeeSat: int64(fee.ToSatoshis()), - FeeMsat: int64(fee), - PaymentPreimage: hex.EncodeToString(preimage[:]), - PaymentRequest: string(payment.Info.PaymentRequest), - Status: status, - Htlcs: htlcs, - PaymentIndex: payment.SequenceNum, - FailureReason: failureReason, + PaymentHash: hex.EncodeToString(paymentID[:]), + Value: satValue, + ValueMsat: msatValue, + ValueSat: satValue, + CreationDate: payment.Info.CreationTime.Unix(), + CreationTimeNs: creationTimeNS, + Fee: int64(fee.ToSatoshis()), + FeeSat: int64(fee.ToSatoshis()), + FeeMsat: int64(fee), + PaymentPreimage: hex.EncodeToString(preimage[:]), + PaymentRequest: string(payment.Info.PaymentRequest), + Status: status, + Htlcs: htlcs, + PaymentIndex: payment.SequenceNum, + FailureReason: failureReason, + FirstHopCustomRecords: payment.Info.FirstHopCustomRecords, }, nil } diff --git a/lnrpc/routerrpc/router_grpc.pb.go b/lnrpc/routerrpc/router_grpc.pb.go index 2259943375..cb68f128e5 100644 --- a/lnrpc/routerrpc/router_grpc.pb.go +++ b/lnrpc/routerrpc/router_grpc.pb.go @@ -116,6 +116,18 @@ type RouterClient interface { // channel to stay disabled until a subsequent manual request of either // "enable" or "auto". UpdateChanStatus(ctx context.Context, in *UpdateChanStatusRequest, opts ...grpc.CallOption) (*UpdateChanStatusResponse, error) + // XAddLocalChanAliases is an experimental API that creates a set of new + // channel SCID alias mappings. The final total set of aliases in the manager + // after the add operation is returned. This is only a locally stored alias, + // and will not be communicated to the channel peer via any message. Therefore, + // routing over such an alias will only work if the peer also calls this same + // RPC on their end. If an alias already exists, an error is returned + XAddLocalChanAliases(ctx context.Context, in *AddAliasesRequest, opts ...grpc.CallOption) (*AddAliasesResponse, error) + // XDeleteLocalChanAliases is an experimental API that deletes a set of alias + // mappings. The final total set of aliases in the manager after the delete + // operation is returned. The deletion will not be communicated to the channel + // peer via any message. + XDeleteLocalChanAliases(ctx context.Context, in *DeleteAliasesRequest, opts ...grpc.CallOption) (*DeleteAliasesResponse, error) } type routerClient struct { @@ -451,6 +463,24 @@ func (c *routerClient) UpdateChanStatus(ctx context.Context, in *UpdateChanStatu return out, nil } +func (c *routerClient) XAddLocalChanAliases(ctx context.Context, in *AddAliasesRequest, opts ...grpc.CallOption) (*AddAliasesResponse, error) { + out := new(AddAliasesResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/XAddLocalChanAliases", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerClient) XDeleteLocalChanAliases(ctx context.Context, in *DeleteAliasesRequest, opts ...grpc.CallOption) (*DeleteAliasesResponse, error) { + out := new(DeleteAliasesResponse) + err := c.cc.Invoke(ctx, "/routerrpc.Router/XDeleteLocalChanAliases", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RouterServer is the server API for Router service. // All implementations must embed UnimplementedRouterServer // for forward compatibility @@ -552,6 +582,18 @@ type RouterServer interface { // channel to stay disabled until a subsequent manual request of either // "enable" or "auto". UpdateChanStatus(context.Context, *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) + // XAddLocalChanAliases is an experimental API that creates a set of new + // channel SCID alias mappings. The final total set of aliases in the manager + // after the add operation is returned. This is only a locally stored alias, + // and will not be communicated to the channel peer via any message. Therefore, + // routing over such an alias will only work if the peer also calls this same + // RPC on their end. If an alias already exists, an error is returned + XAddLocalChanAliases(context.Context, *AddAliasesRequest) (*AddAliasesResponse, error) + // XDeleteLocalChanAliases is an experimental API that deletes a set of alias + // mappings. The final total set of aliases in the manager after the delete + // operation is returned. The deletion will not be communicated to the channel + // peer via any message. + XDeleteLocalChanAliases(context.Context, *DeleteAliasesRequest) (*DeleteAliasesResponse, error) mustEmbedUnimplementedRouterServer() } @@ -613,6 +655,12 @@ func (UnimplementedRouterServer) HtlcInterceptor(Router_HtlcInterceptorServer) e func (UnimplementedRouterServer) UpdateChanStatus(context.Context, *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateChanStatus not implemented") } +func (UnimplementedRouterServer) XAddLocalChanAliases(context.Context, *AddAliasesRequest) (*AddAliasesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method XAddLocalChanAliases not implemented") +} +func (UnimplementedRouterServer) XDeleteLocalChanAliases(context.Context, *DeleteAliasesRequest) (*DeleteAliasesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method XDeleteLocalChanAliases not implemented") +} func (UnimplementedRouterServer) mustEmbedUnimplementedRouterServer() {} // UnsafeRouterServer may be embedded to opt out of forward compatibility for this service. @@ -976,6 +1024,42 @@ func _Router_UpdateChanStatus_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Router_XAddLocalChanAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddAliasesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).XAddLocalChanAliases(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/XAddLocalChanAliases", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).XAddLocalChanAliases(ctx, req.(*AddAliasesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Router_XDeleteLocalChanAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAliasesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RouterServer).XDeleteLocalChanAliases(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/routerrpc.Router/XDeleteLocalChanAliases", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).XDeleteLocalChanAliases(ctx, req.(*DeleteAliasesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Router_ServiceDesc is the grpc.ServiceDesc for Router service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1027,6 +1111,14 @@ var Router_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateChanStatus", Handler: _Router_UpdateChanStatus_Handler, }, + { + MethodName: "XAddLocalChanAliases", + Handler: _Router_XAddLocalChanAliases_Handler, + }, + { + MethodName: "XDeleteLocalChanAliases", + Handler: _Router_XDeleteLocalChanAliases_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index a88bb4d0cf..a4112ba646 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" @@ -55,6 +56,14 @@ var ( errUnexpectedFailureSource = errors.New("unexpected failure source") + // ErrAliasAlreadyExists is returned if a new SCID alias is attempted + // to be added that already exists. + ErrAliasAlreadyExists = errors.New("alias already exists") + + // ErrNoValidAlias is returned if an alias is not in the valid range for + // allowed SCID aliases. + ErrNoValidAlias = errors.New("not a valid alias") + // macaroonOps are the set of capabilities that our minted macaroon (if // it doesn't already exist) will have. macaroonOps = []bakery.Op{ @@ -142,6 +151,14 @@ var ( Entity: "offchain", Action: "write", }}, + "/routerrpc.Router/XAddLocalChanAliases": {{ + Entity: "offchain", + Action: "write", + }}, + "/routerrpc.Router/XDeleteLocalChanAliases": {{ + Entity: "offchain", + Action: "write", + }}, } // DefaultRouterMacFilename is the default name of the router macaroon @@ -511,11 +528,16 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string, AmtMsat: amtMsat, PaymentHash: paymentHash[:], FeeLimitSat: routeFeeLimitSat, - PaymentAddr: payReq.PaymentAddr[:], FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()), DestFeatures: MarshalFeatures(payReq.Features), } + // If the payment addresses is specified, then we'll also populate that + // now as well. + payReq.PaymentAddr.WhenSome(func(addr [32]byte) { + copy(probeRequest.PaymentAddr, addr[:]) + }) + hints := payReq.RouteHints // If the hints don't indicate an LSP then chances are that our probe @@ -847,6 +869,11 @@ func (s *Server) SendToRouteV2(ctx context.Context, return nil, err } + firstHopRecords := lnwire.CustomRecords(req.FirstHopCustomRecords) + if err := firstHopRecords.Validate(); err != nil { + return nil, err + } + var attempt *channeldb.HTLCAttempt // Pass route to the router. This call returns the full htlc attempt @@ -856,9 +883,13 @@ func (s *Server) SendToRouteV2(ctx context.Context, // case, we give precedence to the attempt information as stored in the // db. if req.SkipTempErr { - attempt, err = s.cfg.Router.SendToRouteSkipTempErr(hash, route) + attempt, err = s.cfg.Router.SendToRouteSkipTempErr( + hash, route, firstHopRecords, + ) } else { - attempt, err = s.cfg.Router.SendToRoute(hash, route) + attempt, err = s.cfg.Router.SendToRoute( + hash, route, firstHopRecords, + ) } if attempt != nil { rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt( @@ -1313,7 +1344,7 @@ func (s *Server) trackPayment(subscription routing.ControlTowerSubscriber, // Otherwise, we will log and return the error as the stream has // received an error from the payment lifecycle. - log.Errorf("TrackPayment got error for payment %x: %v", identifier, err) + log.Errorf("TrackPayment got error for payment %v: %v", identifier, err) return err } @@ -1427,12 +1458,12 @@ func (s *Server) BuildRoute(_ context.Context, outgoingChan = &req.OutgoingChanId } - var payAddr *[32]byte + var payAddr fn.Option[[32]byte] if len(req.PaymentAddr) != 0 { var backingPayAddr [32]byte copy(backingPayAddr[:], req.PaymentAddr) - payAddr = &backingPayAddr + payAddr = fn.Some(backingPayAddr) } if req.FinalCltvDelta == 0 { @@ -1441,9 +1472,26 @@ func (s *Server) BuildRoute(_ context.Context, ) } + var firstHopBlob fn.Option[[]byte] + if len(req.FirstHopCustomRecords) > 0 { + firstHopRecords := lnwire.CustomRecords( + req.FirstHopCustomRecords, + ) + if err := firstHopRecords.Validate(); err != nil { + return nil, err + } + + firstHopData, err := firstHopRecords.Serialize() + if err != nil { + return nil, err + } + firstHopBlob = fn.Some(firstHopData) + } + // Build the route and return it to the caller. route, err := s.cfg.Router.BuildRoute( amt, hops, outgoingChan, req.FinalCltvDelta, payAddr, + firstHopBlob, ) if err != nil { return nil, err @@ -1525,12 +1573,131 @@ func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error { } defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0) - // run the forward interceptor. + // Run the forward interceptor. return newForwardInterceptor( s.cfg.RouterBackend.InterceptableForwarder, stream, ).run() } +// XAddLocalChanAliases is an experimental API that creates a set of new +// channel SCID alias mappings. The final total set of aliases in the manager +// after the add operation is returned. This is only a locally stored alias, and +// will not be communicated to the channel peer via any message. Therefore, +// routing over such an alias will only work if the peer also calls this same +// RPC on their end. If an alias already exists, an error is returned. +func (s *Server) XAddLocalChanAliases(_ context.Context, + in *AddAliasesRequest) (*AddAliasesResponse, error) { + + existingAliases := s.cfg.AliasMgr.ListAliases() + + // aliasExists checks if the new alias already exists in the alias map. + aliasExists := func(newAlias uint64, + baseScid lnwire.ShortChannelID) (bool, error) { + + // First check that we actually have a channel for the given + // base scid. This should succeed for any channel where the + // option-scid-alias feature bit was negotiated. + if _, ok := existingAliases[baseScid]; !ok { + return false, fmt.Errorf("base scid %v not found", + baseScid) + } + + for base, aliases := range existingAliases { + for _, alias := range aliases { + exists := alias.ToUint64() == newAlias + + // Trying to add an alias that we already have + // for another channel is wrong. + if exists && base != baseScid { + return true, fmt.Errorf("%w: alias %v "+ + "already exists for base scid "+ + "%v", ErrAliasAlreadyExists, + alias, base) + } + + if exists { + return true, nil + } + } + } + + return false, nil + } + + for _, v := range in.AliasMaps { + baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid) + + for _, rpcAlias := range v.Aliases { + // If not, let's add it to the alias manager now. + aliasScid := lnwire.NewShortChanIDFromInt(rpcAlias) + + // But we only add it, if it's a valid alias, as defined + // by the BOLT spec. + if !aliasmgr.IsAlias(aliasScid) { + return nil, fmt.Errorf("%w: SCID alias %v is "+ + "not a valid alias", ErrNoValidAlias, + aliasScid) + } + + exists, err := aliasExists(rpcAlias, baseScid) + if err != nil { + return nil, err + } + + // If the alias already exists, we see that as an error. + // This is to avoid "silent" collisions. + if exists { + return nil, fmt.Errorf("%w: SCID alias %v "+ + "already exists", ErrAliasAlreadyExists, + rpcAlias) + } + + err = s.cfg.AliasMgr.AddLocalAlias( + aliasScid, baseScid, false, true, + ) + if err != nil { + return nil, fmt.Errorf("error adding scid "+ + "alias, base_scid=%v, alias_scid=%v: "+ + "%w", baseScid, aliasScid, err) + } + } + } + + return &AddAliasesResponse{ + AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()), + }, nil +} + +// XDeleteLocalChanAliases is an experimental API that deletes a set of alias +// mappings. The final total set of aliases in the manager after the delete +// operation is returned. The deletion will not be communicated to the channel +// peer via any message. +func (s *Server) XDeleteLocalChanAliases(_ context.Context, + in *DeleteAliasesRequest) (*DeleteAliasesResponse, + error) { + + for _, v := range in.AliasMaps { + baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid) + + for _, alias := range v.Aliases { + aliasScid := lnwire.NewShortChanIDFromInt(alias) + + err := s.cfg.AliasMgr.DeleteLocalAlias( + aliasScid, baseScid, + ) + if err != nil { + return nil, fmt.Errorf("error deleting scid "+ + "alias, base_scid=%v, alias_scid=%v: "+ + "%w", baseScid, aliasScid, err) + } + } + } + + return &DeleteAliasesResponse{ + AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()), + }, nil +} + func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) { chanPoint := req.GetChanPoint() txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index e70a1a7712..e197a745d6 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -2493,10 +2493,7 @@ func (w *WalletKit) SignMessageWithAddr(_ context.Context, "fetched from wallet database: %w", err) } - sigBytes, err := ecdsa.SignCompact(privKey, digest, pubKey.Compressed()) - if err != nil { - return nil, fmt.Errorf("failed to create signature: %w", err) - } + sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed()) // Bitcoin signatures are base64 encoded (being compatible with // bitcoin-core and btcd). diff --git a/lntest/bitcoind.go b/lntest/bitcoind.go index 37800169bb..6467164828 100644 --- a/lntest/bitcoind.go +++ b/lntest/bitcoind.go @@ -13,7 +13,6 @@ func NewBackend(miner string, netParams *chaincfg.Params) ( *BitcoindBackendConfig, func() error, error) { extraArgs := []string{ - "-debug", "-regtest", "-txindex", "-disablewallet", diff --git a/lntest/bitcoind_common.go b/lntest/bitcoind_common.go index 9cbcd23fde..c66532bbca 100644 --- a/lntest/bitcoind_common.go +++ b/lntest/bitcoind_common.go @@ -115,6 +115,7 @@ func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string, zmqTxAddr := fmt.Sprintf("tcp://127.0.0.1:%d", port.NextAvailablePort()) rpcPort := port.NextAvailablePort() p2pPort := port.NextAvailablePort() + torBindPort := port.NextAvailablePort() cmdArgs := []string{ "-datadir=" + tempBitcoindDir, @@ -124,8 +125,11 @@ func newBackend(miner string, netParams *chaincfg.Params, extraArgs []string, "220110063096c221be9933c82d38e1", fmt.Sprintf("-rpcport=%d", rpcPort), fmt.Sprintf("-port=%d", p2pPort), + fmt.Sprintf("-bind=127.0.0.1:%d=onion", torBindPort), "-zmqpubrawblock=" + zmqBlockAddr, "-zmqpubrawtx=" + zmqTxAddr, + "-debug", + "-debugexclude=libevent", "-debuglogfile=" + logFile, } cmdArgs = append(cmdArgs, extraArgs...) diff --git a/lntest/bitcoind_notxindex.go b/lntest/bitcoind_notxindex.go index de7959eba7..611b89f5ec 100644 --- a/lntest/bitcoind_notxindex.go +++ b/lntest/bitcoind_notxindex.go @@ -13,7 +13,6 @@ func NewBackend(miner string, netParams *chaincfg.Params) ( *BitcoindBackendConfig, func() error, error) { extraArgs := []string{ - "-debug", "-regtest", "-disablewallet", } diff --git a/lntest/bitcoind_rpcpolling.go b/lntest/bitcoind_rpcpolling.go index ca203ad2c2..1280e6f2f4 100644 --- a/lntest/bitcoind_rpcpolling.go +++ b/lntest/bitcoind_rpcpolling.go @@ -13,7 +13,6 @@ func NewBackend(miner string, netParams *chaincfg.Params) ( *BitcoindBackendConfig, func() error, error) { extraArgs := []string{ - "-debug", "-regtest", "-txindex", "-disablewallet", diff --git a/lntest/harness.go b/lntest/harness.go index e8996e9a64..09e2418351 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -16,6 +16,7 @@ import ( "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/kvdb/etcd" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest/miner" @@ -341,6 +342,7 @@ func (h *HarnessTest) SetupStandbyNodes() { // above a good number of confirmations. const totalTxes = 200 h.MineBlocksAndAssertNumTxes(numBlocksSendOutput, totalTxes) + h.MineBlocks(numBlocksSendOutput) // Now we want to wait for the nodes to catch up. h.WaitForBlockchainSync(h.Alice) @@ -912,12 +914,12 @@ func (h *HarnessTest) validateNodeState(hn *node.HarnessNode) error { // GetChanPointFundingTxid takes a channel point and converts it into a chain // hash. func (h *HarnessTest) GetChanPointFundingTxid( - cp *lnrpc.ChannelPoint) *chainhash.Hash { + cp *lnrpc.ChannelPoint) chainhash.Hash { txid, err := lnrpc.GetChanPointFundingTxid(cp) require.NoError(h, err, "unable to get txid") - return txid + return *txid } // OutPointFromChannelPoint creates an outpoint from a given channel point. @@ -926,7 +928,7 @@ func (h *HarnessTest) OutPointFromChannelPoint( txid := h.GetChanPointFundingTxid(cp) return wire.OutPoint{ - Hash: *txid, + Hash: txid, Index: cp.OutputIndex, } } @@ -1262,7 +1264,7 @@ func (h *HarnessTest) OpenChannelAssertErr(srcNode, destNode *node.HarnessNode, // mempool. func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, - force bool) (rpc.CloseChanClient, *chainhash.Hash) { + force bool) (rpc.CloseChanClient, chainhash.Hash) { // Calls the rpc to close the channel. closeReq := &lnrpc.CloseChannelRequest{ @@ -1305,9 +1307,9 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, pendingClose.ClosePending.Txid) // Assert the closing tx is in the mempool. - h.miner.AssertTxInMempool(closeTxid) + h.miner.AssertTxInMempool(*closeTxid) - return stream, closeTxid + return stream, *closeTxid } // CloseChannel attempts to coop close a non-anchored channel identified by the @@ -1320,7 +1322,7 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, // 5. the node reports zero waiting close channels. // 6. the node receives a topology update regarding the channel close. func (h *HarnessTest) CloseChannel(hn *node.HarnessNode, - cp *lnrpc.ChannelPoint) *chainhash.Hash { + cp *lnrpc.ChannelPoint) chainhash.Hash { stream, _ := h.CloseChannelAssertPending(hn, cp, false) @@ -1339,7 +1341,7 @@ func (h *HarnessTest) CloseChannel(hn *node.HarnessNode, // 7. mine DefaultCSV-1 blocks. // 8. the node reports zero pending force close channels. func (h *HarnessTest) ForceCloseChannel(hn *node.HarnessNode, - cp *lnrpc.ChannelPoint) *chainhash.Hash { + cp *lnrpc.ChannelPoint) chainhash.Hash { stream, _ := h.CloseChannelAssertPending(hn, cp, true) @@ -1901,7 +1903,7 @@ func (h *HarnessTest) CalculateTxFee(tx *wire.MsgTx) btcutil.Amount { var balance btcutil.Amount for _, in := range tx.TxIn { parentHash := in.PreviousOutPoint.Hash - rawTx := h.miner.GetRawTransaction(&parentHash) + rawTx := h.miner.GetRawTransaction(parentHash) parent := rawTx.MsgTx() value := parent.TxOut[in.PreviousOutPoint.Index].Value @@ -2071,8 +2073,42 @@ func (h *HarnessTest) ReceiveHtlcInterceptor( require.Fail(h, "timeout", "timeout intercepting htlc") case err := <-errChan: - require.Failf(h, "err from stream", - "received err from stream: %v", err) + require.Failf(h, "err from HTLC interceptor stream", + "received err from HTLC interceptor stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} + +// ReceiveInvoiceHtlcModification waits until a message is received on the +// invoice HTLC modifier stream or the timeout is reached. +func (h *HarnessTest) ReceiveInvoiceHtlcModification( + stream rpc.InvoiceHtlcModifierClient) *invoicesrpc.HtlcModifyRequest { + + chanMsg := make(chan *invoicesrpc.HtlcModifyRequest) + errChan := make(chan error) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout invoice HTLC modifier") + + case err := <-errChan: + require.Failf(h, "err from invoice HTLC modifier stream", + "received err from invoice HTLC modifier stream: %v", + err) case updateMsg := <-chanMsg: return updateMsg @@ -2116,7 +2152,7 @@ func (h *HarnessTest) ReceiveChannelEvent( // GetOutputIndex returns the output index of the given address in the given // transaction. -func (h *HarnessTest) GetOutputIndex(txid *chainhash.Hash, addr string) int { +func (h *HarnessTest) GetOutputIndex(txid chainhash.Hash, addr string) int { // We'll then extract the raw transaction from the mempool in order to // determine the index of the p2tr output. tx := h.miner.GetRawTransaction(txid) diff --git a/lntest/harness_assertion.go b/lntest/harness_assertion.go index 615263373c..d56e70703f 100644 --- a/lntest/harness_assertion.go +++ b/lntest/harness_assertion.go @@ -534,7 +534,7 @@ func (h *HarnessTest) AssertTopologyChannelClosed(hn *node.HarnessNode, // by consuming a message from the passed close channel stream. Returns the // closing txid if found. func (h HarnessTest) WaitForChannelCloseEvent( - stream rpc.CloseChanClient) *chainhash.Hash { + stream rpc.CloseChanClient) chainhash.Hash { // Consume one event. event, err := h.ReceiveCloseChannelUpdate(stream) @@ -548,7 +548,7 @@ func (h HarnessTest) WaitForChannelCloseEvent( require.NoErrorf(h, err, "wrong format found in closing txid: %v", resp.ChanClose.ClosingTxid) - return txid + return *txid } // AssertNumWaitingClose checks that a PendingChannels response from the node @@ -634,7 +634,7 @@ func (h *HarnessTest) AssertNumPendingForceClose(hn *node.HarnessNode, // - assert the node has seen the channel close update. func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, anchors bool, - stream rpc.CloseChanClient) *chainhash.Hash { + stream rpc.CloseChanClient) chainhash.Hash { // Assert the channel is waiting close. resp := h.AssertChannelWaitingClose(hn, cp) @@ -682,7 +682,7 @@ func (h *HarnessTest) AssertStreamChannelCoopClosed(hn *node.HarnessNode, // confirmed. func (h *HarnessTest) AssertStreamChannelForceClosed(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, anchorSweep bool, - stream rpc.CloseChanClient) *chainhash.Hash { + stream rpc.CloseChanClient) chainhash.Hash { // Assert the channel is waiting close. resp := h.AssertChannelWaitingClose(hn, cp) @@ -964,6 +964,11 @@ func (h *HarnessTest) AssertChannelBalanceResp(hn *node.HarnessNode, expected *lnrpc.ChannelBalanceResponse) { resp := hn.RPC.ChannelBalance() + + // Ignore custom channel data of both expected and actual responses. + expected.CustomChannelData = nil + resp.CustomChannelData = nil + require.True(h, proto.Equal(expected, resp), "balance is incorrect "+ "got: %v, want: %v", resp, expected) } @@ -1600,13 +1605,16 @@ func (h *HarnessTest) findPayment(hn *node.HarnessNode, return nil } +// PaymentCheck is a function that checks a payment for a specific condition. +type PaymentCheck func(*lnrpc.Payment) error + // AssertPaymentStatus asserts that the given node list a payment with the // given preimage has the expected status. It also checks that the payment has // the expected preimage, which is empty when it's not settled and matches the // given preimage when it's succeeded. func (h *HarnessTest) AssertPaymentStatus(hn *node.HarnessNode, - preimage lntypes.Preimage, - status lnrpc.Payment_PaymentStatus) *lnrpc.Payment { + preimage lntypes.Preimage, status lnrpc.Payment_PaymentStatus, + checks ...PaymentCheck) *lnrpc.Payment { var target *lnrpc.Payment payHash := preimage.Hash() @@ -1636,6 +1644,11 @@ func (h *HarnessTest) AssertPaymentStatus(hn *node.HarnessNode, target.PaymentPreimage, "expected zero preimage") } + // Perform any additional checks on the payment. + for _, check := range checks { + require.NoError(h, check(target)) + } + return target } diff --git a/lntest/harness_miner.go b/lntest/harness_miner.go index bc9aef1805..73619a9fbf 100644 --- a/lntest/harness_miner.go +++ b/lntest/harness_miner.go @@ -112,9 +112,7 @@ func (h *HarnessTest) MineBlocksAndAssertNumTxes(num uint32, } // Make sure the mempool has been updated. - for _, txid := range txids { - h.miner.AssertTxNotInMempool(*txid) - } + h.miner.AssertTxnsNotInMempool(txids) // Finally, make sure all the active nodes are synced. bestBlock := blocks[len(blocks)-1] @@ -202,7 +200,7 @@ func (h *HarnessTest) mineTillForceCloseResolved(hn *node.HarnessNode) { } // AssertTxInMempool asserts a given transaction can be found in the mempool. -func (h *HarnessTest) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { +func (h *HarnessTest) AssertTxInMempool(txid chainhash.Hash) *wire.MsgTx { return h.miner.AssertTxInMempool(txid) } @@ -212,14 +210,14 @@ func (h *HarnessTest) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { // NOTE: this should be used after `AssertTxInMempool` to ensure the tx has // entered the mempool before. Otherwise it might give false positive and the // tx may enter the mempool after the check. -func (h *HarnessTest) AssertTxNotInMempool(txid chainhash.Hash) *wire.MsgTx { - return h.miner.AssertTxNotInMempool(txid) +func (h *HarnessTest) AssertTxNotInMempool(txid chainhash.Hash) { + h.miner.AssertTxNotInMempool(txid) } // AssertNumTxsInMempool polls until finding the desired number of transactions // in the provided miner's mempool. It will asserrt if this number is not met // after the given timeout. -func (h *HarnessTest) AssertNumTxsInMempool(n int) []*chainhash.Hash { +func (h *HarnessTest) AssertNumTxsInMempool(n int) []chainhash.Hash { return h.miner.AssertNumTxsInMempool(n) } @@ -230,7 +228,7 @@ func (h *HarnessTest) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx { // AssertTxInBlock asserts that a given txid can be found in the passed block. func (h *HarnessTest) AssertTxInBlock(block *wire.MsgBlock, - txid *chainhash.Hash) { + txid chainhash.Hash) { h.miner.AssertTxInBlock(block, txid) } @@ -263,13 +261,13 @@ func (h *HarnessTest) DisconnectFromMiner(tempMiner *miner.HarnessMiner) { // GetRawMempool makes a RPC call to the miner's GetRawMempool and // asserts. -func (h *HarnessTest) GetRawMempool() []*chainhash.Hash { +func (h *HarnessTest) GetRawMempool() []chainhash.Hash { return h.miner.GetRawMempool() } // GetRawTransaction makes a RPC call to the miner's GetRawTransaction and // asserts. -func (h *HarnessTest) GetRawTransaction(txid *chainhash.Hash) *btcutil.Tx { +func (h *HarnessTest) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx { return h.miner.GetRawTransaction(txid) } diff --git a/lntest/miner/miner.go b/lntest/miner/miner.go index bc05f542fd..3877e2f0dd 100644 --- a/lntest/miner/miner.go +++ b/lntest/miner/miner.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lntest/node" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/stretchr/testify/require" @@ -155,11 +156,16 @@ func (h *HarnessMiner) GetBestBlock() (*chainhash.Hash, int32) { // GetRawMempool makes a RPC call to the miner's GetRawMempool and // asserts. -func (h *HarnessMiner) GetRawMempool() []*chainhash.Hash { +func (h *HarnessMiner) GetRawMempool() []chainhash.Hash { mempool, err := h.Client.GetRawMempool() require.NoError(h, err, "unable to get mempool") - return mempool + txns := make([]chainhash.Hash, 0, len(mempool)) + for _, txid := range mempool { + txns = append(txns, *txid) + } + + return txns } // GenerateBlocks mine 'num' of blocks and returns them. @@ -197,9 +203,9 @@ func (h *HarnessMiner) MineBlocks(num uint32) []*wire.MsgBlock { // AssertNumTxsInMempool polls until finding the desired number of transactions // in the provided miner's mempool. It will asserrt if this number is not met // after the given timeout. -func (h *HarnessMiner) AssertNumTxsInMempool(n int) []*chainhash.Hash { +func (h *HarnessMiner) AssertNumTxsInMempool(n int) []chainhash.Hash { var ( - mem []*chainhash.Hash + mem []chainhash.Hash err error ) @@ -221,7 +227,7 @@ func (h *HarnessMiner) AssertNumTxsInMempool(n int) []*chainhash.Hash { // AssertTxInBlock asserts that a given txid can be found in the passed block. func (h *HarnessMiner) AssertTxInBlock(block *wire.MsgBlock, - txid *chainhash.Hash) { + txid chainhash.Hash) { blockTxes := make([]chainhash.Hash, 0) @@ -263,8 +269,8 @@ func (h *HarnessMiner) MineBlocksAndAssertNumTxes(num uint32, // GetRawTransaction makes a RPC call to the miner's GetRawTransaction and // asserts. -func (h *HarnessMiner) GetRawTransaction(txid *chainhash.Hash) *btcutil.Tx { - tx, err := h.Client.GetRawTransaction(txid) +func (h *HarnessMiner) GetRawTransaction(txid chainhash.Hash) *btcutil.Tx { + tx, err := h.Client.GetRawTransaction(&txid) require.NoErrorf(h, err, "failed to get raw tx: %v", txid) return tx } @@ -272,15 +278,15 @@ func (h *HarnessMiner) GetRawTransaction(txid *chainhash.Hash) *btcutil.Tx { // GetRawTransactionVerbose makes a RPC call to the miner's // GetRawTransactionVerbose and asserts. func (h *HarnessMiner) GetRawTransactionVerbose( - txid *chainhash.Hash) *btcjson.TxRawResult { + txid chainhash.Hash) *btcjson.TxRawResult { - tx, err := h.Client.GetRawTransactionVerbose(txid) + tx, err := h.Client.GetRawTransactionVerbose(&txid) require.NoErrorf(h, err, "failed to get raw tx verbose: %v", txid) return tx } // AssertTxInMempool asserts a given transaction can be found in the mempool. -func (h *HarnessMiner) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { +func (h *HarnessMiner) AssertTxInMempool(txid chainhash.Hash) *wire.MsgTx { var msgTx *wire.MsgTx err := wait.NoError(func() error { @@ -294,7 +300,7 @@ func (h *HarnessMiner) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { for _, memTx := range mempool { // Check the values are equal. - if *memTx == *txid { + if memTx == txid { return nil } } @@ -308,15 +314,41 @@ func (h *HarnessMiner) AssertTxInMempool(txid *chainhash.Hash) *wire.MsgTx { return msgTx } +// AssertTxnsNotInMempool asserts the given txns are not found in the mempool. +// It assumes the mempool is not empty. +func (h *HarnessMiner) AssertTxnsNotInMempool(txids []chainhash.Hash) { + err := wait.NoError(func() error { + // We require the RPC call to be succeeded and won't wait for + // it as it's an unexpected behavior. + mempool := h.GetRawMempool() + + // Turn the mempool into a txn set for faster lookups. + mempoolTxns := fn.NewSet(mempool...) + + // Check if any of the txids are in the mempool. + for _, txid := range txids { + // Skip if the tx is not in the mempool. + if !mempoolTxns.Contains(txid) { + continue + } + + return fmt.Errorf("expect txid %v to be NOT found in "+ + "mempool", txid) + } + + return nil + }, wait.MinerMempoolTimeout) + + require.NoError(h, err, "timeout checking txns not in mempool") +} + // AssertTxNotInMempool asserts a given transaction cannot be found in the // mempool. It assumes the mempool is not empty. // // NOTE: this should be used after `AssertTxInMempool` to ensure the tx has // entered the mempool before. Otherwise it might give false positive and the // tx may enter the mempool after the check. -func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) *wire.MsgTx { - var msgTx *wire.MsgTx - +func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) { err := wait.NoError(func() error { // We require the RPC call to be succeeded and won't wait for // it as it's an unexpected behavior. @@ -324,7 +356,7 @@ func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) *wire.MsgTx { for _, memTx := range mempool { // Check the values are equal. - if txid.IsEqual(memTx) { + if txid == memTx { return fmt.Errorf("expect txid %v to be NOT "+ "found in mempool", txid) } @@ -334,8 +366,6 @@ func (h *HarnessMiner) AssertTxNotInMempool(txid chainhash.Hash) *wire.MsgTx { }, wait.MinerMempoolTimeout) require.NoError(h, err, "timeout checking tx not in mempool") - - return msgTx } // SendOutputsWithoutChange uses the miner to send the given outputs using the @@ -417,7 +447,7 @@ func (h *HarnessMiner) AssertOutpointInMempool(op wire.OutPoint) *wire.MsgTx { // found. For instance, the aggregation logic used in // sweeping HTLC outputs will update the mempool by // replacing the HTLC spending txes with a single one. - tx, err := h.Client.GetRawTransaction(txid) + tx, err := h.Client.GetRawTransaction(&txid) if err != nil { return err } diff --git a/lntest/mock/secretkeyring.go b/lntest/mock/secretkeyring.go index 56c0fac948..a5a39cc729 100644 --- a/lntest/mock/secretkeyring.go +++ b/lntest/mock/secretkeyring.go @@ -69,7 +69,8 @@ func (s *SecretKeyRing) SignMessageCompact(_ keychain.KeyLocator, } else { digest = chainhash.HashB(msg) } - return ecdsa.SignCompact(s.RootKey, digest, true) + + return ecdsa.SignCompact(s.RootKey, digest, true), nil } // SignMessageSchnorr signs the passed message and ignores the KeyDescriptor. diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index 7af22e0383..87fce9cba5 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -78,9 +78,8 @@ func (w *WalletController) ConfirmedBalance(int32, string) (btcutil.Amount, func (w *WalletController) NewAddress(lnwallet.AddressType, bool, string) (btcutil.Address, error) { - addr, _ := btcutil.NewAddressPubKey( - w.RootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams, - ) + pkh := btcutil.Hash160(w.RootKey.PubKey().SerializeCompressed()) + addr, _ := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams) return addr, nil } diff --git a/lntest/node/harness_node.go b/lntest/node/harness_node.go index 3df5a8e64d..7415dfed29 100644 --- a/lntest/node/harness_node.go +++ b/lntest/node/harness_node.go @@ -6,7 +6,6 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "os" @@ -661,8 +660,7 @@ func (hn *HarnessNode) WaitForProcessExit() error { break case <-time.After(wait.DefaultTimeout): - err = errors.New("timeout waiting for process to exit") - hn.printErrf(err.Error()) + hn.printErrf("timeout waiting for process to exit") } // Make sure log file is closed and renamed if necessary. diff --git a/lntest/rpc/harness_rpc.go b/lntest/rpc/harness_rpc.go index 0e8256ce11..0640581dcd 100644 --- a/lntest/rpc/harness_rpc.go +++ b/lntest/rpc/harness_rpc.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" + "github.com/lightningnetwork/lnd/lnrpc/devrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" "github.com/lightningnetwork/lnd/lnrpc/peersrpc" @@ -42,6 +43,7 @@ type HarnessRPC struct { ChainKit chainrpc.ChainKitClient NeutrinoKit neutrinorpc.NeutrinoKitClient Peer peersrpc.PeersClient + DevRPC devrpc.DevClient // Name is the HarnessNode's name. Name string @@ -73,6 +75,7 @@ func NewHarnessRPC(ctxt context.Context, t *testing.T, c *grpc.ClientConn, ChainKit: chainrpc.NewChainKitClient(c), NeutrinoKit: neutrinorpc.NewNeutrinoKitClient(c), Peer: peersrpc.NewPeersClient(c), + DevRPC: devrpc.NewDevClient(c), Name: name, } diff --git a/lntest/rpc/invoices.go b/lntest/rpc/invoices.go index 5b771968e8..a5c560897f 100644 --- a/lntest/rpc/invoices.go +++ b/lntest/rpc/invoices.go @@ -5,7 +5,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" ) // ===================== @@ -84,18 +83,19 @@ func (h *HarnessRPC) SubscribeSingleInvoice(rHash []byte) SingleInvoiceClient { return client } -type TrackPaymentClient routerrpc.Router_TrackPaymentV2Client +type InvoiceHtlcModifierClient invoicesrpc.Invoices_HtlcModifierClient -// TrackPaymentV2 creates a subscription client for given invoice and -// asserts its creation. -func (h *HarnessRPC) TrackPaymentV2(payHash []byte) TrackPaymentClient { - req := &routerrpc.TrackPaymentRequest{PaymentHash: payHash} +// InvoiceHtlcModifier makes an RPC call to the node's RouterClient and asserts. +func (h *HarnessRPC) InvoiceHtlcModifier() (InvoiceHtlcModifierClient, + context.CancelFunc) { - // TrackPaymentV2 needs to have the context alive for the entire test - // case as the returned client will be used for send and receive events - // stream. Thus we use runCtx here instead of a timeout context. - client, err := h.Router.TrackPaymentV2(h.runCtx, req) - h.NoError(err, "TrackPaymentV2") + // InvoiceHtlcModifier needs to have the context alive for the entire + // test case as the returned client will be used for send and receive + // events stream. Therefore, we use cancel context here instead of a + // timeout context. + ctxt, cancel := context.WithCancel(h.runCtx) + resp, err := h.Invoice.HtlcModifier(ctxt) + h.NoError(err, "InvoiceHtlcModifier") - return client + return resp, cancel } diff --git a/lntest/rpc/router.go b/lntest/rpc/router.go index 8bb8a92e39..707c43a11d 100644 --- a/lntest/rpc/router.go +++ b/lntest/rpc/router.go @@ -208,6 +208,54 @@ func (h *HarnessRPC) HtlcInterceptor() (InterceptorClient, context.CancelFunc) { return resp, cancel } +// XAddLocalChanAliases adds a list of aliases to the node's alias map. +func (h *HarnessRPC) XAddLocalChanAliases(req *routerrpc.AddAliasesRequest) { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XAddLocalChanAliases(ctxt, req) + h.NoError(err, "XAddLocalChanAliases") +} + +// XAddLocalChanAliasesErr adds a list of aliases to the node's alias map and +// expects an error. +func (h *HarnessRPC) XAddLocalChanAliasesErr( + req *routerrpc.AddAliasesRequest) error { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XAddLocalChanAliases(ctxt, req) + require.Error(h, err) + + return err +} + +// XDeleteLocalChanAliases deleted a set of alias mappings. +func (h *HarnessRPC) XDeleteLocalChanAliases( + req *routerrpc.DeleteAliasesRequest) { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XDeleteLocalChanAliases(ctxt, req) + h.NoError(err, "XDeleteLocalChanAliases") +} + +// XDeleteLocalChanAliasesErr deleted a set of alias mappings and expects an +// error. +func (h *HarnessRPC) XDeleteLocalChanAliasesErr( + req *routerrpc.DeleteAliasesRequest) error { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.Router.XDeleteLocalChanAliases(ctxt, req) + require.Error(h, err) + + return err +} + type TrackPaymentsClient routerrpc.Router_TrackPaymentsClient // TrackPayments makes a RPC call to the node's RouterClient and asserts. @@ -219,3 +267,19 @@ func (h *HarnessRPC) TrackPayments( return resp } + +type TrackPaymentClient routerrpc.Router_TrackPaymentV2Client + +// TrackPaymentV2 creates a subscription client for given invoice and +// asserts its creation. +func (h *HarnessRPC) TrackPaymentV2(payHash []byte) TrackPaymentClient { + req := &routerrpc.TrackPaymentRequest{PaymentHash: payHash} + + // TrackPaymentV2 needs to have the context alive for the entire test + // case as the returned client will be used for send and receive events + // stream. Thus we use runCtx here instead of a timeout context. + client, err := h.Router.TrackPaymentV2(h.runCtx, req) + h.NoError(err, "TrackPaymentV2") + + return client +} diff --git a/lntest/unittest/backend.go b/lntest/unittest/backend.go index be09b395c3..ac700044d2 100644 --- a/lntest/unittest/backend.go +++ b/lntest/unittest/backend.go @@ -82,6 +82,7 @@ func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params, tempBitcoindDir := t.TempDir() rpcPort := port.NextAvailablePort() + torBindPort := port.NextAvailablePort() zmqBlockPort := port.NextAvailablePort() zmqTxPort := port.NextAvailablePort() zmqBlockHost := fmt.Sprintf("tcp://127.0.0.1:%d", zmqBlockPort) @@ -94,6 +95,7 @@ func NewBitcoindBackend(t *testing.T, netParams *chaincfg.Params, "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a95" + "284294edb5773b05544b220110063096c221be9933c82d38e1", fmt.Sprintf("-rpcport=%d", rpcPort), + fmt.Sprintf("-bind=127.0.0.1:%d=onion", torBindPort), "-disablewallet", "-zmqpubrawblock=" + zmqBlockHost, "-zmqpubrawtx=" + zmqTxHost, diff --git a/lntypes/channel_party.go b/lntypes/channel_party.go index be800541bd..5848becee6 100644 --- a/lntypes/channel_party.go +++ b/lntypes/channel_party.go @@ -50,3 +50,70 @@ func (p ChannelParty) IsLocal() bool { func (p ChannelParty) IsRemote() bool { return p == Remote } + +// Dual represents a structure when we are tracking the same parameter for both +// the Local and Remote parties. +type Dual[A any] struct { + // Local is the value tracked for the Local ChannelParty. + Local A + + // Remote is the value tracked for the Remote ChannelParty. + Remote A +} + +// GetForParty gives Dual an access method that takes a ChannelParty as an +// argument. It is included for ergonomics in cases where the ChannelParty is +// in a variable and which party determines how we want to access the Dual. +func (d *Dual[A]) GetForParty(p ChannelParty) A { + switch p { + case Local: + return d.Local + case Remote: + return d.Remote + default: + panic(fmt.Sprintf( + "switch default triggered in ForParty: %v", p, + )) + } +} + +// SetForParty sets the value in the Dual for the given ChannelParty. This +// returns a copy of the original value. +func (d *Dual[A]) SetForParty(p ChannelParty, value A) { + switch p { + case Local: + d.Local = value + case Remote: + d.Remote = value + default: + panic(fmt.Sprintf( + "switch default triggered in ForParty: %v", p, + )) + } +} + +// ModifyForParty applies the function argument to the given ChannelParty field +// and returns a new copy of the Dual. +func (d *Dual[A]) ModifyForParty(p ChannelParty, f func(A) A) A { + switch p { + case Local: + d.Local = f(d.Local) + return d.Local + case Remote: + d.Remote = f(d.Remote) + return d.Remote + default: + panic(fmt.Sprintf( + "switch default triggered in ForParty: %v", p, + )) + } +} + +// MapDual applies the function argument to both the Local and Remote fields of +// the Dual[A] and returns a Dual[B] with that function applied. +func MapDual[A, B any](d Dual[A], f func(A) B) Dual[B] { + return Dual[B]{ + Local: f(d.Local), + Remote: f(d.Remote), + } +} diff --git a/lnwallet/aux_leaf_store.go b/lnwallet/aux_leaf_store.go new file mode 100644 index 0000000000..c457a92509 --- /dev/null +++ b/lnwallet/aux_leaf_store.go @@ -0,0 +1,240 @@ +package lnwallet + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" +) + +// CommitSortFunc is a function type alias for a function that sorts the +// commitment transaction outputs. The second parameter is a list of CLTV +// timeouts that must correspond to the number of transaction outputs, with the +// value of 0 for non-HTLC outputs. The HTLC indexes are needed to have a +// deterministic sort value for HTLCs that have the identical amount, CLTV +// timeout and payment hash (e.g. multiple MPP shards of the same payment, where +// the on-chain script would be identical). +type CommitSortFunc func(tx *wire.MsgTx, cltvs []uint32, + indexes []input.HtlcIndex) error + +// DefaultCommitSort is the default commitment sort function that sorts the +// commitment transaction inputs and outputs according to BIP69. The second +// parameter is a list of CLTV timeouts that must correspond to the number of +// transaction outputs, with the value of 0 for non-HTLC outputs. The third +// parameter is unused for the default sort function. +func DefaultCommitSort(tx *wire.MsgTx, cltvs []uint32, + _ []input.HtlcIndex) error { + + InPlaceCommitSort(tx, cltvs) + return nil +} + +// CommitAuxLeaves stores two potential auxiliary leaves for the remote and +// local output that may be used to augment the final tapscript trees of the +// commitment transaction. +type CommitAuxLeaves struct { + // LocalAuxLeaf is the local party's auxiliary leaf. + LocalAuxLeaf input.AuxTapLeaf + + // RemoteAuxLeaf is the remote party's auxiliary leaf. + RemoteAuxLeaf input.AuxTapLeaf + + // OutgoingHTLCLeaves is the set of aux leaves for the outgoing HTLCs + // on this commitment transaction. + OutgoingHtlcLeaves input.HtlcAuxLeaves + + // IncomingHTLCLeaves is the set of aux leaves for the incoming HTLCs + // on this commitment transaction. + IncomingHtlcLeaves input.HtlcAuxLeaves +} + +// AuxChanState is a struct that holds certain fields of the +// channeldb.OpenChannel struct that are used by the aux components. The data +// is copied over to prevent accidental mutation of the original channel state. +type AuxChanState struct { + // ChanType denotes which type of channel this is. + ChanType channeldb.ChannelType + + // FundingOutpoint is the outpoint of the final funding transaction. + // This value uniquely and globally identifies the channel within the + // target blockchain as specified by the chain hash parameter. + FundingOutpoint wire.OutPoint + + // ShortChannelID encodes the exact location in the chain in which the + // channel was initially confirmed. This includes: the block height, + // transaction index, and the output within the target transaction. + // + // If IsZeroConf(), then this will the "base" (very first) ALIAS scid + // and the confirmed SCID will be stored in ConfirmedScid. + ShortChannelID lnwire.ShortChannelID + + // IsInitiator is a bool which indicates if we were the original + // initiator for the channel. This value may affect how higher levels + // negotiate fees, or close the channel. + IsInitiator bool + + // Capacity is the total capacity of this channel. + Capacity btcutil.Amount + + // LocalChanCfg is the channel configuration for the local node. + LocalChanCfg channeldb.ChannelConfig + + // RemoteChanCfg is the channel configuration for the remote node. + RemoteChanCfg channeldb.ChannelConfig + + // ThawHeight is the height when a frozen channel once again becomes a + // normal channel. If this is zero, then there're no restrictions on + // this channel. If the value is lower than 500,000, then it's + // interpreted as a relative height, or an absolute height otherwise. + ThawHeight uint32 + + // TapscriptRoot is an optional tapscript root used to derive the MuSig2 + // funding output. + TapscriptRoot fn.Option[chainhash.Hash] + + // CustomBlob is an optional blob that can be used to store information + // specific to a custom channel type. This information is only created + // at channel funding time, and after wards is to be considered + // immutable. + CustomBlob fn.Option[tlv.Blob] +} + +// NewAuxChanState creates a new AuxChanState from the given channel state. +func NewAuxChanState(chanState *channeldb.OpenChannel) AuxChanState { + return AuxChanState{ + ChanType: chanState.ChanType, + FundingOutpoint: chanState.FundingOutpoint, + ShortChannelID: chanState.ShortChannelID, + IsInitiator: chanState.IsInitiator, + Capacity: chanState.Capacity, + LocalChanCfg: chanState.LocalChanCfg, + RemoteChanCfg: chanState.RemoteChanCfg, + ThawHeight: chanState.ThawHeight, + TapscriptRoot: chanState.TapscriptRoot, + CustomBlob: chanState.CustomBlob, + } +} + +// CommitDiffAuxInput is the input required to compute the diff of the auxiliary +// leaves for a commitment transaction. +type CommitDiffAuxInput struct { + // ChannelState is the static channel information of the channel this + // commitment transaction relates to. + ChannelState AuxChanState + + // PrevBlob is the blob of the previous commitment transaction. + PrevBlob tlv.Blob + + // UnfilteredView is the unfiltered, original HTLC view of the channel. + // Unfiltered in this context means that the view contains all HTLCs, + // including the canceled ones. + UnfilteredView *HtlcView + + // WhoseCommit denotes whose commitment transaction we are computing the + // diff for. + WhoseCommit lntypes.ChannelParty + + // OurBalance is the balance of the local party. + OurBalance lnwire.MilliSatoshi + + // TheirBalance is the balance of the remote party. + TheirBalance lnwire.MilliSatoshi + + // KeyRing is the key ring that can be used to derive keys for the + // commitment transaction. + KeyRing CommitmentKeyRing +} + +// CommitDiffAuxResult is the result of computing the diff of the auxiliary +// leaves for a commitment transaction. +type CommitDiffAuxResult struct { + // AuxLeaves are the auxiliary leaves for the new commitment + // transaction. + AuxLeaves fn.Option[CommitAuxLeaves] + + // CommitSortFunc is an optional function that sorts the commitment + // transaction inputs and outputs. + CommitSortFunc fn.Option[CommitSortFunc] +} + +// AuxLeafStore is used to optionally fetch auxiliary tapscript leaves for the +// commitment transaction given an opaque blob. This is also used to implement +// a state transition function for the blobs to allow them to be refreshed with +// each state. +type AuxLeafStore interface { + // FetchLeavesFromView attempts to fetch the auxiliary leaves that + // correspond to the passed aux blob, and pending original (unfiltered) + // HTLC view. + FetchLeavesFromView( + in CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] + + // FetchLeavesFromCommit attempts to fetch the auxiliary leaves that + // correspond to the passed aux blob, and an existing channel + // commitment. + FetchLeavesFromCommit(chanState AuxChanState, + commit channeldb.ChannelCommitment, + keyRing CommitmentKeyRing, whoseCommit lntypes.ChannelParty, + ) fn.Result[CommitDiffAuxResult] + + // FetchLeavesFromRevocation attempts to fetch the auxiliary leaves + // from a channel revocation that stores balance + blob information. + FetchLeavesFromRevocation( + r *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult] + + // ApplyHtlcView serves as the state transition function for the custom + // channel's blob. Given the old blob, and an HTLC view, then a new + // blob should be returned that reflects the pending updates. + ApplyHtlcView(in CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] +} + +// auxLeavesFromView is used to derive the set of commit aux leaves (if any), +// that are needed to create a new commitment transaction using the original +// (unfiltered) htlc view. +func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, + prevBlob fn.Option[tlv.Blob], originalView *HtlcView, + whoseCommit lntypes.ChannelParty, ourBalance, + theirBalance lnwire.MilliSatoshi, + keyRing CommitmentKeyRing) fn.Result[CommitDiffAuxResult] { + + return fn.MapOptionZ( + prevBlob, func(blob tlv.Blob) fn.Result[CommitDiffAuxResult] { + return leafStore.FetchLeavesFromView(CommitDiffAuxInput{ + ChannelState: NewAuxChanState(chanState), + PrevBlob: blob, + UnfilteredView: originalView, + WhoseCommit: whoseCommit, + OurBalance: ourBalance, + TheirBalance: theirBalance, + KeyRing: keyRing, + }) + }, + ) +} + +// updateAuxBlob is a helper function that attempts to update the aux blob +// given the prior and current state information. +func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, + prevBlob fn.Option[tlv.Blob], nextViewUnfiltered *HtlcView, + whoseCommit lntypes.ChannelParty, ourBalance, + theirBalance lnwire.MilliSatoshi, + keyRing CommitmentKeyRing) fn.Result[fn.Option[tlv.Blob]] { + + return fn.MapOptionZ( + prevBlob, func(blob tlv.Blob) fn.Result[fn.Option[tlv.Blob]] { + return leafStore.ApplyHtlcView(CommitDiffAuxInput{ + ChannelState: NewAuxChanState(chanState), + PrevBlob: blob, + UnfilteredView: nextViewUnfiltered, + WhoseCommit: whoseCommit, + OurBalance: ourBalance, + TheirBalance: theirBalance, + KeyRing: keyRing, + }) + }, + ) +} diff --git a/lnwallet/aux_resolutions.go b/lnwallet/aux_resolutions.go new file mode 100644 index 0000000000..382232640d --- /dev/null +++ b/lnwallet/aux_resolutions.go @@ -0,0 +1,125 @@ +package lnwallet + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" + "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 +) + +// AuxSigDesc stores optional information related to 2nd level HTLCs for aux +// channels. +type AuxSigDesc struct { + // AuxSig is the second-level signature for the HTLC that we are trying + // to resolve. This is only present if this is a resolution request for + // an HTLC on our commitment transaction. + AuxSig []byte + + // SignDetails is the sign details for the second-level HTLC. This may + // be used to generate the second signature needed for broadcast. + SignDetails input.SignDetails +} + +// 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 + + // ChanType is the type of the channel that we are trying to resolve. + ChanType channeldb.ChannelType + + // 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] + + // HtlcID is the ID of the HTLC that we are trying to resolve. This is + // only set if this is a resolution request for an HTLC. + HtlcID fn.Option[input.HtlcIndex] + + // HtlcAmt is the amount of the HTLC that we are trying to resolve. + HtlcAmt btcutil.Amount + + // 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 + + // CommitCsvDelay is the CSV delay for the remote output for this + // commitment. + CommitCsvDelay 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/transaction. + CltvDelay fn.Option[uint32] + + // PayHash is the payment hash for the HTLC that we are trying to + // resolve. This is optional as it only applies HTLC outputs. + PayHash fn.Option[[32]byte] + + // AuxSigDesc is an optional field that contains additional information + // needed to sweep second level HTLCs. + AuxSigDesc fn.Option[AuxSigDesc] +} + +// 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/aux_signer.go b/lnwallet/aux_signer.go new file mode 100644 index 0000000000..5d4bc79241 --- /dev/null +++ b/lnwallet/aux_signer.go @@ -0,0 +1,250 @@ +package lnwallet + +import ( + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" +) + +// htlcCustomSigType is the TLV type that is used to encode the custom HTLC +// signatures within the custom data for an existing HTLC. +var htlcCustomSigType tlv.TlvType65543 + +// AuxHtlcDescriptor is a struct that contains the information needed to sign or +// verify an HTLC for custom channels. +type AuxHtlcDescriptor struct { + // ChanID is the ChannelID of the LightningChannel that this + // paymentDescriptor belongs to. We track this here so we can + // reconstruct the Messages that this paymentDescriptor is built from. + ChanID lnwire.ChannelID + + // RHash is the payment hash for this HTLC. The HTLC can be settled iff + // the preimage to this hash is presented. + RHash PaymentHash + + // Timeout is the absolute timeout in blocks, after which this HTLC + // expires. + Timeout uint32 + + // Amount is the HTLC amount in milli-satoshis. + Amount lnwire.MilliSatoshi + + // HtlcIndex is the index within the main update log for this HTLC. + // Entries within the log of type Add will have this field populated, + // as other entries will point to the entry via this counter. + // + // NOTE: This field will only be populated if EntryType is Add. + HtlcIndex uint64 + + // ParentIndex is the HTLC index of the entry that this update settles + // or times out. + // + // NOTE: This field will only be populated if EntryType is Fail or + // Settle. + ParentIndex uint64 + + // EntryType denotes the exact type of the paymentDescriptor. In the + // case of a Timeout, or Settle type, then the Parent field will point + // into the log to the HTLC being modified. + EntryType updateType + + // CustomRecords also stores the set of optional custom records that + // may have been attached to a sent HTLC. + CustomRecords lnwire.CustomRecords + + // addCommitHeight[Remote|Local] encodes the height of the commitment + // which included this HTLC on either the remote or local commitment + // chain. This value is used to determine when an HTLC is fully + // "locked-in". + addCommitHeightRemote uint64 + addCommitHeightLocal uint64 + + // removeCommitHeight[Remote|Local] encodes the height of the + // commitment which removed the parent pointer of this + // paymentDescriptor either due to a timeout or a settle. Once both + // these heights are below the tail of both chains, the log entries can + // safely be removed. + removeCommitHeightRemote uint64 + removeCommitHeightLocal uint64 +} + +// AddHeight returns the height at which the HTLC was added to the commitment +// chain. The height is returned based on the chain the HTLC is being added to +// (local or remote chain). +func (a *AuxHtlcDescriptor) AddHeight( + whoseCommitChain lntypes.ChannelParty) uint64 { + + if whoseCommitChain.IsRemote() { + return a.addCommitHeightRemote + } + + return a.addCommitHeightLocal +} + +// RemoveHeight returns the height at which the HTLC was removed from the +// commitment chain. The height is returned based on the chain the HTLC is being +// removed from (local or remote chain). +func (a *AuxHtlcDescriptor) RemoveHeight( + whoseCommitChain lntypes.ChannelParty) uint64 { + + if whoseCommitChain.IsRemote() { + return a.removeCommitHeightRemote + } + + return a.removeCommitHeightLocal +} + +// newAuxHtlcDescriptor creates a new AuxHtlcDescriptor from a payment +// descriptor. +func newAuxHtlcDescriptor(p *paymentDescriptor) AuxHtlcDescriptor { + return AuxHtlcDescriptor{ + ChanID: p.ChanID, + RHash: p.RHash, + Timeout: p.Timeout, + Amount: p.Amount, + HtlcIndex: p.HtlcIndex, + ParentIndex: p.ParentIndex, + EntryType: p.EntryType, + CustomRecords: p.CustomRecords.Copy(), + addCommitHeightRemote: p.addCommitHeightRemote, + addCommitHeightLocal: p.addCommitHeightLocal, + removeCommitHeightRemote: p.removeCommitHeightRemote, + removeCommitHeightLocal: p.removeCommitHeightLocal, + } +} + +// BaseAuxJob is a struct that contains the common fields that are shared among +// the aux sign/verify jobs. +type BaseAuxJob struct { + // OutputIndex is the output index of the HTLC on the commitment + // transaction being signed. + // + // NOTE: If the output is dust from the PoV of the commitment chain, + // then this value will be -1. + OutputIndex int32 + + // KeyRing is the commitment key ring that contains the keys needed to + // generate the second level HTLC signatures. + KeyRing CommitmentKeyRing + + // HTLC is the HTLC that is being signed or verified. + HTLC AuxHtlcDescriptor + + // Incoming is a boolean that indicates if the HTLC is incoming or + // outgoing. + Incoming bool + + // CommitBlob is the commitment transaction blob that contains the aux + // information for this channel. + CommitBlob fn.Option[tlv.Blob] + + // HtlcLeaf is the aux tap leaf that corresponds to the HTLC being + // signed/verified. + HtlcLeaf input.AuxTapLeaf +} + +// AuxSigJob is a struct that contains all the information needed to sign an +// HTLC for custom channels. +type AuxSigJob struct { + // SignDesc is the sign desc for this HTLC. + SignDesc input.SignDescriptor + + BaseAuxJob + + // Resp is a channel that will be used to send the result of the sign + // job. This channel MUST be buffered. + Resp chan AuxSigJobResp + + // Cancel is a channel that is closed by the caller if they wish to + // abandon all pending sign jobs part of a single batch. This should + // never be closed by the validator. + Cancel <-chan struct{} +} + +// NewAuxSigJob creates a new AuxSigJob. +func NewAuxSigJob(sigJob SignJob, keyRing CommitmentKeyRing, incoming bool, + htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob], + htlcLeaf input.AuxTapLeaf, cancelChan <-chan struct{}) AuxSigJob { + + return AuxSigJob{ + SignDesc: sigJob.SignDesc, + BaseAuxJob: BaseAuxJob{ + OutputIndex: sigJob.OutputIndex, + KeyRing: keyRing, + HTLC: htlc, + Incoming: incoming, + CommitBlob: commitBlob, + HtlcLeaf: htlcLeaf, + }, + Resp: make(chan AuxSigJobResp, 1), + Cancel: cancelChan, + } +} + +// AuxSigJobResp is a struct that contains the result of a sign job. +type AuxSigJobResp struct { + // SigBlob is the signature blob that was generated for the HTLC. This + // is an opaque TLV field that may contain the signature and other data. + SigBlob fn.Option[tlv.Blob] + + // HtlcIndex is the index of the HTLC that was signed. + HtlcIndex uint64 + + // Err is the error that occurred when executing the specified + // signature job. In the case that no error occurred, this value will + // be nil. + Err error +} + +// AuxVerifyJob is a struct that contains all the information needed to verify +// an HTLC for custom channels. +type AuxVerifyJob struct { + // SigBlob is the signature blob that was generated for the HTLC. This + // is an opaque TLV field that may contain the signature and other data. + SigBlob fn.Option[tlv.Blob] + + BaseAuxJob +} + +// NewAuxVerifyJob creates a new AuxVerifyJob. +func NewAuxVerifyJob(sig fn.Option[tlv.Blob], keyRing CommitmentKeyRing, + incoming bool, htlc AuxHtlcDescriptor, commitBlob fn.Option[tlv.Blob], + htlcLeaf input.AuxTapLeaf) AuxVerifyJob { + + return AuxVerifyJob{ + SigBlob: sig, + BaseAuxJob: BaseAuxJob{ + KeyRing: keyRing, + HTLC: htlc, + Incoming: incoming, + CommitBlob: commitBlob, + HtlcLeaf: htlcLeaf, + }, + } +} + +// AuxSigner is an interface that is used to sign and verify HTLCs for custom +// channels. It is similar to the existing SigPool, but uses opaque blobs to +// shuffle around signature information and other metadata. +type AuxSigner interface { + // SubmitSecondLevelSigBatch takes a batch of aux sign jobs and + // processes them asynchronously. + SubmitSecondLevelSigBatch(chanState AuxChanState, commitTx *wire.MsgTx, + sigJob []AuxSigJob) error + + // PackSigs takes a series of aux signatures and packs them into a + // single blob that can be sent alongside the CommitSig messages. + PackSigs([]fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] + + // UnpackSigs takes a packed blob of signatures and returns the + // original signatures for each HTLC, keyed by HTLC index. + UnpackSigs(fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] + + // VerifySecondLevelSigs attempts to synchronously verify a batch of aux + // sig jobs. + VerifySecondLevelSigs(chanState AuxChanState, commitTx *wire.MsgTx, + verifyJob []AuxVerifyJob) error +} diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index b3f9b4f9b1..e796c4322c 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -1688,7 +1688,6 @@ out: } } } - }(txNtfn) // Launch a goroutine to re-package and send diff --git a/lnwallet/chancloser/aux_closer.go b/lnwallet/chancloser/aux_closer.go new file mode 100644 index 0000000000..8b1c445ca3 --- /dev/null +++ b/lnwallet/chancloser/aux_closer.go @@ -0,0 +1,104 @@ +package chancloser + +import ( + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" +) + +// CloseOutput represents an output that should be included in the close +// transaction. +type CloseOutput struct { + // Amt is the amount of the output. + Amt btcutil.Amount + + // DustLimit is the dust limit for the local node. + DustLimit btcutil.Amount + + // PkScript is the script that should be used to pay to the output. + PkScript []byte + + // ShutdownRecords is the set of custom records that may result in + // extra close outputs being added. + ShutdownRecords lnwire.CustomRecords +} + +// AuxShutdownReq is used to request a set of extra custom records to include +// in the shutdown message. +type AuxShutdownReq struct { + // ChanPoint is the channel point of the channel that is being shut + // down. + ChanPoint wire.OutPoint + + // ShortChanID is the short channel ID of the channel that is being + // closed. + ShortChanID lnwire.ShortChannelID + + // Initiator is true if the local node is the initiator of the channel. + Initiator bool + + // InternalKey is the internal key for the shutdown addr. This will + // only be set for taproot shutdown addrs. + InternalKey fn.Option[btcec.PublicKey] + + // CommitBlob is the blob that was included in the last commitment. + CommitBlob fn.Option[tlv.Blob] + + // FundingBlob is the blob that was included in the funding state. + FundingBlob fn.Option[tlv.Blob] +} + +// AuxCloseDesc is used to describe the channel close that is being performed. +type AuxCloseDesc struct { + AuxShutdownReq + + // CloseFee is the closing fee to be paid for this state. + CloseFee btcutil.Amount + + // CommitFee is the fee that was paid for the last commitment. + CommitFee btcutil.Amount + + // LocalCloseOutput is the output that the local node should be paid + // to. This is None if the local party will not have an output on the + // co-op close transaction. + LocalCloseOutput fn.Option[CloseOutput] + + // RemoteCloseOutput is the output that the remote node should be paid + // to. This will be None if the remote party will not have an output on + // the co-op close transaction. + RemoteCloseOutput fn.Option[CloseOutput] +} + +// AuxCloseOutputs is used to specify extra outputs that should be used when +// constructing the co-op close transaction. +type AuxCloseOutputs struct { + // ExtraCloseOutputs is a set of extra outputs that should be included + // in the close transaction. + ExtraCloseOutputs []lnwallet.CloseOutput + + // CustomSort is a custom function that can be used to sort the + // transaction outputs. If this isn't set, then the default BIP-69 + // sorting is used. + CustomSort lnwallet.CloseSortFunc +} + +// AuxChanCloser is used to allow an external caller to modify the co-op close +// transaction. +type AuxChanCloser interface { + // ShutdownBlob returns the set of custom records that should be + // included in the shutdown message. + ShutdownBlob(req AuxShutdownReq) (fn.Option[lnwire.CustomRecords], + error) + + // AuxCloseOutputs returns the set of custom outputs that should be used + // to construct the co-op close transaction. + AuxCloseOutputs(desc AuxCloseDesc) (fn.Option[AuxCloseOutputs], error) + + // FinalizeClose is called after the close transaction has been agreed + // upon. + FinalizeClose(desc AuxCloseDesc, closeTx *wire.MsgTx) error +} diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 57033d4b36..662a38aa76 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -106,6 +107,18 @@ const ( defaultMaxFeeMultiplier = 3 ) +// DeliveryAddrWithKey wraps a normal delivery addr, but also includes the +// internal key for the delivery addr if known. +type DeliveryAddrWithKey struct { + // DeliveryAddress is the raw, serialized pkScript of the delivery + // address. + lnwire.DeliveryAddress + + // InternalKey is the Taproot internal key of the delivery address, if + // the address is a P2TR output. + InternalKey fn.Option[btcec.PublicKey] +} + // ChanCloseCfg holds all the items that a ChanCloser requires to carry out its // duties. type ChanCloseCfg struct { @@ -140,6 +153,10 @@ type ChanCloseCfg struct { // FeeEstimator is used to estimate the absolute starting co-op close // fee. FeeEstimator CoopFeeEstimator + + // AuxCloser is an optional interface that can be used to modify the + // way the co-op close process proceeds. + AuxCloser fn.Option[AuxChanCloser] } // ChanCloser is a state machine that handles the cooperative channel closure @@ -204,6 +221,10 @@ type ChanCloser struct { // funds to. localDeliveryScript []byte + // localInternalKey is the local delivery address Taproot internal key, + // if the local delivery script is a P2TR output. + localInternalKey fn.Option[btcec.PublicKey] + // remoteDeliveryScript is the script that we'll send the remote party's // settled channel funds to. remoteDeliveryScript []byte @@ -215,6 +236,20 @@ type ChanCloser struct { // we use to handle a specific race condition caused by the independent // message processing queues. cachedClosingSigned fn.Option[lnwire.ClosingSigned] + + // localCloseOutput is the local output on the closing transaction that + // the local party should be paid to. This will only be populated if the + // local balance isn't dust. + localCloseOutput fn.Option[CloseOutput] + + // remoteCloseOutput is the remote output on the closing transaction + // that the remote party should be paid to. This will only be populated + // if the remote balance isn't dust. + remoteCloseOutput fn.Option[CloseOutput] + + // auxOutputs are the optional additional outputs that might be added to + // the closing transaction. + auxOutputs fn.Option[AuxCloseOutputs] } // calcCoopCloseFee computes an "ideal" absolute co-op close fee given the @@ -266,7 +301,7 @@ func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType, // NewChanCloser creates a new instance of the channel closure given the passed // configuration, and delivery+fee preference. The final argument should only // be populated iff, we're the initiator of this closing request. -func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, +func NewChanCloser(cfg ChanCloseCfg, deliveryScript DeliveryAddrWithKey, idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32, closeReq *htlcswitch.ChanClose, closer lntypes.ChannelParty) *ChanCloser { @@ -281,7 +316,8 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte, cfg: cfg, negotiationHeight: negotiationHeight, idealFeeRate: idealFeePerKw, - localDeliveryScript: deliveryScript, + localInternalKey: deliveryScript.InternalKey, + localDeliveryScript: deliveryScript.DeliveryAddress, priorFeeOffers: make( map[btcutil.Amount]*lnwire.ClosingSigned, ), @@ -295,13 +331,13 @@ func (c *ChanCloser) initFeeBaseline() { // Depending on if a balance ends up being dust or not, we'll pass a // nil TxOut into the EstimateFee call which can handle it. var localTxOut, remoteTxOut *wire.TxOut - if !c.cfg.Channel.LocalBalanceDust() { + if isDust, _ := c.cfg.Channel.LocalBalanceDust(); !isDust { localTxOut = &wire.TxOut{ PkScript: c.localDeliveryScript, Value: 0, } } - if !c.cfg.Channel.RemoteBalanceDust() { + if isDust, _ := c.cfg.Channel.RemoteBalanceDust(); !isDust { remoteTxOut = &wire.TxOut{ PkScript: c.remoteDeliveryScript, Value: 0, @@ -337,6 +373,31 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { // desired closing script. shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript) + // At this point, we'll check to see if we have any custom records to + // add to the shutdown message. + err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error { + shutdownCustomRecords, err := a.ShutdownBlob(AuxShutdownReq{ + ChanPoint: c.chanPoint, + ShortChanID: c.cfg.Channel.ShortChanID(), + Initiator: c.cfg.Channel.IsInitiator(), + InternalKey: c.localInternalKey, + CommitBlob: c.cfg.Channel.LocalCommitmentBlob(), + FundingBlob: c.cfg.Channel.FundingBlob(), + }) + if err != nil { + return err + } + + shutdownCustomRecords.WhenSome(func(cr lnwire.CustomRecords) { + shutdown.CustomRecords = cr + }) + + return nil + }) + if err != nil { + return nil, err + } + // If this is a taproot channel, then we'll need to also generate a // nonce that'll be used sign the co-op close transaction offer. if c.cfg.Channel.ChanType().IsTaproot() { @@ -370,11 +431,22 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { shutdownInfo := channeldb.NewShutdownInfo( c.localDeliveryScript, c.closer.IsLocal(), ) - err := c.cfg.Channel.MarkShutdownSent(shutdownInfo) + err = c.cfg.Channel.MarkShutdownSent(shutdownInfo) if err != nil { return nil, err } + // We'll track our local close output, even if it's dust in BTC terms, + // it might still carry value in custom channel terms. + _, dustAmt := c.cfg.Channel.LocalBalanceDust() + localBalance, _ := c.cfg.Channel.CommitBalances() + c.localCloseOutput = fn.Some(CloseOutput{ + Amt: localBalance, + DustLimit: dustAmt, + PkScript: c.localDeliveryScript, + ShutdownRecords: shutdown.CustomRecords, + }) + return shutdown, nil } @@ -444,6 +516,21 @@ func (c *ChanCloser) NegotiationHeight() uint32 { return c.negotiationHeight } +// LocalCloseOutput returns the local close output. +func (c *ChanCloser) LocalCloseOutput() fn.Option[CloseOutput] { + return c.localCloseOutput +} + +// RemoteCloseOutput returns the remote close output. +func (c *ChanCloser) RemoteCloseOutput() fn.Option[CloseOutput] { + return c.remoteCloseOutput +} + +// AuxOutputs returns optional extra outputs. +func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] { + return c.auxOutputs +} + // validateShutdownScript attempts to match and validate the script provided in // our peer's shutdown message with the upfront shutdown script we have on // record. For any script specified, we also make sure it matches our @@ -503,6 +590,17 @@ func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) ( noShutdown := fn.None[lnwire.Shutdown]() + // We'll track their remote close output, even if it's dust in BTC + // terms, it might still carry value in custom channel terms. + _, dustAmt := c.cfg.Channel.RemoteBalanceDust() + _, remoteBalance := c.cfg.Channel.CommitBalances() + c.remoteCloseOutput = fn.Some(CloseOutput{ + Amt: remoteBalance, + DustLimit: dustAmt, + PkScript: msg.Address, + ShutdownRecords: msg.CustomRecords, + }) + switch c.state { // If we're in the close idle state, and we're receiving a channel // closure related message, then this indicates that we're on the @@ -850,6 +948,25 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen } } + // Before we complete the cooperative close, we'll see if we + // have any extra aux options. + c.auxOutputs, err = c.auxCloseOutputs(remoteProposedFee) + if err != nil { + return noClosing, err + } + c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) { + closeOpts = append( + closeOpts, lnwallet.WithExtraCloseOutputs( + outs.ExtraCloseOutputs, + ), + ) + closeOpts = append( + closeOpts, lnwallet.WithCustomCoopSort( + outs.CustomSort, + ), + ) + }) + closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript, remoteProposedFee, closeOpts..., @@ -859,6 +976,33 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen } c.closingTx = closeTx + // If there's an aux chan closer, then we'll finalize with it + // before we write to disk. + err = fn.MapOptionZ( + c.cfg.AuxCloser, func(aux AuxChanCloser) error { + channel := c.cfg.Channel + //nolint:lll + req := AuxShutdownReq{ + ChanPoint: c.chanPoint, + ShortChanID: c.cfg.Channel.ShortChanID(), + InternalKey: c.localInternalKey, + Initiator: channel.IsInitiator(), + CommitBlob: channel.LocalCommitmentBlob(), + FundingBlob: channel.FundingBlob(), + } + desc := AuxCloseDesc{ + AuxShutdownReq: req, + LocalCloseOutput: c.localCloseOutput, + RemoteCloseOutput: c.remoteCloseOutput, + } + + return aux.FinalizeClose(desc, closeTx) + }, + ) + if err != nil { + return noClosing, err + } + // Before publishing the closing tx, we persist it to the // database, such that it can be republished if something goes // wrong. @@ -908,9 +1052,46 @@ func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen } } +// auxCloseOutputs returns any additional outputs that should be used when +// closing the channel. +func (c *ChanCloser) auxCloseOutputs( + closeFee btcutil.Amount) (fn.Option[AuxCloseOutputs], error) { + + var closeOuts fn.Option[AuxCloseOutputs] + err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error { + req := AuxShutdownReq{ + ChanPoint: c.chanPoint, + ShortChanID: c.cfg.Channel.ShortChanID(), + InternalKey: c.localInternalKey, + Initiator: c.cfg.Channel.IsInitiator(), + CommitBlob: c.cfg.Channel.LocalCommitmentBlob(), + FundingBlob: c.cfg.Channel.FundingBlob(), + } + outs, err := aux.AuxCloseOutputs(AuxCloseDesc{ + AuxShutdownReq: req, + CloseFee: closeFee, + CommitFee: c.cfg.Channel.CommitFee(), + LocalCloseOutput: c.localCloseOutput, + RemoteCloseOutput: c.remoteCloseOutput, + }) + if err != nil { + return err + } + + closeOuts = outs + + return nil + }) + if err != nil { + return closeOuts, err + } + + return closeOuts, nil +} + // proposeCloseSigned attempts to propose a new signature for the closing -// transaction for a channel based on the prior fee negotiations and our current -// compromise fee. +// transaction for a channel based on the prior fee negotiations and our +// current compromise fee. func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) ( *lnwire.ClosingSigned, error) { @@ -928,6 +1109,26 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) ( } } + // We'll also now see if the aux chan closer has any additional options + // for the closing purpose. + c.auxOutputs, err = c.auxCloseOutputs(fee) + if err != nil { + return nil, err + } + c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) { + closeOpts = append( + closeOpts, lnwallet.WithExtraCloseOutputs( + outs.ExtraCloseOutputs, + ), + ) + closeOpts = append( + closeOpts, lnwallet.WithCustomCoopSort( + outs.CustomSort, + ), + ) + }) + + // With all our options added, we'll attempt to co-op close now. rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( fee, c.localDeliveryScript, c.remoteDeliveryScript, closeOpts..., diff --git a/lnwallet/chancloser/chancloser_test.go b/lnwallet/chancloser/chancloser_test.go index 9a90d0ab2b..28709fd5f8 100644 --- a/lnwallet/chancloser/chancloser_test.go +++ b/lnwallet/chancloser/chancloser_test.go @@ -14,6 +14,7 @@ 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/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -21,6 +22,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -151,6 +153,14 @@ func (m *mockChannel) ChannelPoint() wire.OutPoint { return m.chanPoint } +func (m *mockChannel) LocalCommitmentBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + +func (m *mockChannel) FundingBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error { @@ -178,8 +188,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress { } func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount, - localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt, -) (input.Signature, *chainhash.Hash, btcutil.Amount, error) { + localScript, remoteScript []byte, + _ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash, + btcutil.Amount, error) { if m.chanType.IsTaproot() { return lnwallet.NewMusigPartialSig( @@ -188,6 +199,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount, R: new(btcec.PublicKey), }, lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil, + fn.None[chainhash.Hash](), ), nil, 0, nil } @@ -202,12 +214,20 @@ func (m *mockChannel) CompleteCooperativeClose(localSig, return &wire.MsgTx{}, 0, nil } -func (m *mockChannel) LocalBalanceDust() bool { - return false +func (m *mockChannel) LocalBalanceDust() (bool, btcutil.Amount) { + return false, 0 +} + +func (m *mockChannel) RemoteBalanceDust() (bool, btcutil.Amount) { + return false, 0 +} + +func (m *mockChannel) CommitBalances() (btcutil.Amount, btcutil.Amount) { + return 0, 0 } -func (m *mockChannel) RemoteBalanceDust() bool { - return false +func (m *mockChannel) CommitFee() btcutil.Amount { + return 0 } func (m *mockChannel) ChanType() channeldb.ChannelType { @@ -341,7 +361,8 @@ func TestMaxFeeClamp(t *testing.T) { Channel: &channel, MaxFee: test.inputMaxFee, FeeEstimator: &SimpleCoopFeeEstimator{}, - }, nil, test.idealFee, 0, nil, lntypes.Remote, + }, DeliveryAddrWithKey{}, test.idealFee, 0, nil, + lntypes.Remote, ) // We'll call initFeeBaseline early here since we need @@ -382,7 +403,8 @@ func TestMaxFeeBailOut(t *testing.T) { MaxFee: idealFee * 2, } chanCloser := NewChanCloser( - closeCfg, nil, idealFee, 0, nil, lntypes.Remote, + closeCfg, DeliveryAddrWithKey{}, idealFee, 0, + nil, lntypes.Remote, ) // We'll now force the channel state into the @@ -506,7 +528,7 @@ func TestTaprootFastClose(t *testing.T) { DisableChannel: func(wire.OutPoint) error { return nil }, - }, nil, idealFee, 0, nil, lntypes.Local, + }, DeliveryAddrWithKey{}, idealFee, 0, nil, lntypes.Local, ) aliceCloser.initFeeBaseline() @@ -523,7 +545,7 @@ func TestTaprootFastClose(t *testing.T) { DisableChannel: func(wire.OutPoint) error { return nil }, - }, nil, idealFee, 0, nil, lntypes.Remote, + }, DeliveryAddrWithKey{}, idealFee, 0, nil, lntypes.Remote, ) bobCloser.initFeeBaseline() diff --git a/lnwallet/chancloser/interface.go b/lnwallet/chancloser/interface.go index 2e9fa98ae8..729cdc545b 100644 --- a/lnwallet/chancloser/interface.go +++ b/lnwallet/chancloser/interface.go @@ -6,11 +6,13 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" ) // CoopFeeEstimator is used to estimate the fee of a co-op close transaction. @@ -32,6 +34,14 @@ type Channel interface { //nolint:interfacebloat // ChannelPoint returns the channel point of the target channel. ChannelPoint() wire.OutPoint + // LocalCommitmentBlob may return the auxiliary data storage blob for + // the local commitment transaction. + LocalCommitmentBlob() fn.Option[tlv.Blob] + + // FundingBlob may return the auxiliary data storage blob related to + // funding details for the channel. + FundingBlob() fn.Option[tlv.Blob] + // MarkCoopBroadcasted persistently marks that the channel close // transaction has been broadcast. MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error @@ -60,13 +70,23 @@ type Channel interface { //nolint:interfacebloat // LocalBalanceDust returns true if when creating a co-op close // transaction, the balance of the local party will be dust after - // accounting for any anchor outputs. - LocalBalanceDust() bool + // accounting for any anchor outputs. The dust value for the local + // party is also returned. + LocalBalanceDust() (bool, btcutil.Amount) // RemoteBalanceDust returns true if when creating a co-op close // transaction, the balance of the remote party will be dust after - // accounting for any anchor outputs. - RemoteBalanceDust() bool + // accounting for any anchor outputs. The dust value the remote party + // is also returned. + RemoteBalanceDust() (bool, btcutil.Amount) + + // CommitBalances returns the local and remote balances in the current + // commitment state. + CommitBalances() (btcutil.Amount, btcutil.Amount) + + // CommitFee returns the commitment fee for the current commitment + // state. + CommitFee() btcutil.Amount // RemoteUpfrontShutdownScript returns the upfront shutdown script of // the remote party. If the remote party didn't specify such a script, diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 21dd473394..b3457f21bf 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -4,8 +4,11 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "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" ) @@ -56,6 +59,14 @@ type ShimIntent struct { // generate an aggregate key to use as the taproot-native multi-sig // output. musig2 bool + + // tapscriptRoot is the root of the tapscript tree that will be used to + // create the funding output. This field will only be utilized if the + // MuSig2 flag above is set to true. + // + // TODO(roasbeef): fold above into new chan type? sum type like thing, + // includes the tapscript root, etc + tapscriptRoot fn.Option[chainhash.Hash] } // FundingOutput returns the witness script, and the output that creates the @@ -76,9 +87,8 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) { // Similar to the existing p2wsh script, we'll always ensure // the keys are sorted before use. return input.GenTaprootFundingScript( - s.localKey.PubKey, - s.remoteKey, - int64(totalAmt), + s.localKey.PubKey, s.remoteKey, int64(totalAmt), + s.tapscriptRoot, ) } @@ -89,6 +99,26 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) { ) } +// TaprootInternalKey may return the internal key for a MuSig2 funding output, +// but only if this is actually a MuSig2 channel. +func (s *ShimIntent) TaprootInternalKey() fn.Option[*btcec.PublicKey] { + if !s.musig2 { + return fn.None[*btcec.PublicKey]() + } + + // Similar to the existing p2wsh script, we'll always ensure the keys + // are sorted before use. Since we're only interested in the internal + // key, we don't need to take into account any tapscript root. + // + // We ignore the error here as this is only called after FundingOutput + // is called. + combinedKey, _, _, _ := musig2.AggregateKeys( + []*btcec.PublicKey{s.localKey.PubKey, s.remoteKey}, true, + ) + + return fn.Some(combinedKey.PreTweakedKey) +} + // Cancel allows the caller to cancel a funding Intent at any time. This will // return any resources such as coins back to the eligible pool to be used in // order channel fundings. diff --git a/lnwallet/chanfunding/coin_select_test.go b/lnwallet/chanfunding/coin_select_test.go index a6ff18baec..26a257b37f 100644 --- a/lnwallet/chanfunding/coin_select_test.go +++ b/lnwallet/chanfunding/coin_select_test.go @@ -874,7 +874,7 @@ func TestCoinSelectUpToAmount(t *testing.T) { defaultChanFundingChangeType, ) if len(test.expectErr) == 0 && err != nil { - t.Fatalf(err.Error()) + t.Fatal(err.Error()) } if changeAmt != test.expectedChange { t.Fatalf("expected %v change amt, got %v", diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/interface.go similarity index 96% rename from lnwallet/chanfunding/assembler.go rename to lnwallet/chanfunding/interface.go index a694d2a2c4..ed7197973e 100644 --- a/lnwallet/chanfunding/assembler.go +++ b/lnwallet/chanfunding/interface.go @@ -4,9 +4,11 @@ import ( "time" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -119,6 +121,11 @@ type Request struct { // output. By definition, this'll also use segwit v1 (taproot) for the // funding output. Musig2 bool + + // TapscriptRoot is the root of the tapscript tree that will be used to + // create the funding output. This field will only be utilized if the + // Musig2 flag above is set to true. + TapscriptRoot fn.Option[chainhash.Hash] } // Intent is returned by an Assembler and represents the base functionality the diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go index 885fb7b465..f678f520fc 100644 --- a/lnwallet/chanfunding/psbt_assembler.go +++ b/lnwallet/chanfunding/psbt_assembler.go @@ -6,11 +6,14 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "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" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -162,6 +165,13 @@ func (i *PsbtIntent) BindKeys(localKey *keychain.KeyDescriptor, i.State = PsbtOutputKnown } +// BindTapscriptRoot takes an optional tapscript root and binds it to the +// underlying funding intent. This only applies to musig2 channels, and will be +// used to make the musig2 funding output. +func (i *PsbtIntent) BindTapscriptRoot(root fn.Option[chainhash.Hash]) { + i.tapscriptRoot = root +} + // FundingParams returns the parameters that are necessary to start funding the // channel output this intent was created for. It returns the P2WSH funding // address, the exact funding amount and a PSBT packet that contains exactly one @@ -208,7 +218,18 @@ func (i *PsbtIntent) FundingParams() (btcutil.Address, int64, *psbt.Packet, } } packet.UnsignedTx.TxOut = append(packet.UnsignedTx.TxOut, out) - packet.Outputs = append(packet.Outputs, psbt.POutput{}) + + var pOut psbt.POutput + + // If this is a MuSig2 channel, we also need to communicate the internal + // key to the caller. Otherwise, they cannot verify the construction of + // the P2TR output script. + pOut.TaprootInternalKey = fn.MapOptionZ( + i.TaprootInternalKey(), schnorr.SerializePubKey, + ) + + packet.Outputs = append(packet.Outputs, pOut) + return addr, out.Value, packet, nil } @@ -534,6 +555,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) { ShimIntent: ShimIntent{ localFundingAmt: p.fundingAmt, musig2: req.Musig2, + tapscriptRoot: req.TapscriptRoot, }, State: PsbtShimRegistered, BasePsbt: p.basePsbt, diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 4f2aa759b2..da78df49f2 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -394,7 +394,6 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) { // we will call the specialized coin selection function for // that. case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0: - // We need to ensure that manually selected coins, which // are spent entirely on the channel funding, leave // enough funds in the wallet to cover for a reserve. @@ -539,6 +538,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) { localFundingAmt: localContributionAmt, remoteFundingAmt: r.RemoteAmt, musig2: r.Musig2, + tapscriptRoot: r.TapscriptRoot, }, InputCoins: selectedCoins, coinLeaser: w.cfg.CoinLeaser, diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 737c1d92e9..e2cc3e0540 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2,11 +2,13 @@ package lnwallet import ( "bytes" + "cmp" + "context" "crypto/sha256" "errors" "fmt" "math" - "sort" + "slices" "sync" "github.com/btcsuite/btcd/blockchain" @@ -33,6 +35,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" + "github.com/lightningnetwork/lnd/tlv" ) var ( @@ -136,6 +139,10 @@ var ( // errNoPartialSig is returned when a partial signature is required, // but none is found. errNoPartialSig = errors.New("no partial signature found") + + // errQuit is returned when a quit signal was received, interrupting the + // current operation. + errQuit = errors.New("received quit signal") ) // ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid @@ -167,99 +174,6 @@ func (e *ErrCommitSyncLocalDataLoss) Error() string { // payments requested by the wallet/daemon. type PaymentHash [32]byte -// PayDescsFromRemoteLogUpdates converts a slice of LogUpdates received from the -// remote peer into PaymentDescriptors to inform a link's forwarding decisions. -// -// NOTE: The provided `logUpdates` MUST correspond exactly to either the Adds -// or SettleFails in this channel's forwarding package at `height`. -func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64, - logUpdates []channeldb.LogUpdate) ([]*PaymentDescriptor, error) { - - // Allocate enough space to hold all of the payment descriptors we will - // reconstruct, and also the list of pointers that will be returned to - // the caller. - payDescs := make([]PaymentDescriptor, 0, len(logUpdates)) - payDescPtrs := make([]*PaymentDescriptor, 0, len(logUpdates)) - - // Iterate over the log updates we loaded from disk, and reconstruct the - // payment descriptor corresponding to one of the four types of htlcs we - // can receive from the remote peer. We only repopulate the information - // necessary to process the packets and, if necessary, forward them to - // the switch. - // - // For each log update, we include either an AddRef or a SettleFailRef - // so that they can be ACK'd and garbage collected. - for i, logUpdate := range logUpdates { - var pd PaymentDescriptor - switch wireMsg := logUpdate.UpdateMsg.(type) { - - case *lnwire.UpdateAddHTLC: - pd = PaymentDescriptor{ - RHash: wireMsg.PaymentHash, - Timeout: wireMsg.Expiry, - Amount: wireMsg.Amount, - EntryType: Add, - HtlcIndex: wireMsg.ID, - LogIndex: logUpdate.LogIndex, - SourceRef: &channeldb.AddRef{ - Height: height, - Index: uint16(i), - }, - BlindingPoint: wireMsg.BlindingPoint, - } - pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob)) - copy(pd.OnionBlob[:], wireMsg.OnionBlob[:]) - - case *lnwire.UpdateFulfillHTLC: - pd = PaymentDescriptor{ - RPreimage: wireMsg.PaymentPreimage, - ParentIndex: wireMsg.ID, - EntryType: Settle, - DestRef: &channeldb.SettleFailRef{ - Source: chanID, - Height: height, - Index: uint16(i), - }, - } - - case *lnwire.UpdateFailHTLC: - pd = PaymentDescriptor{ - ParentIndex: wireMsg.ID, - EntryType: Fail, - FailReason: wireMsg.Reason[:], - DestRef: &channeldb.SettleFailRef{ - Source: chanID, - Height: height, - Index: uint16(i), - }, - } - - case *lnwire.UpdateFailMalformedHTLC: - pd = PaymentDescriptor{ - ParentIndex: wireMsg.ID, - EntryType: MalformedFail, - FailCode: wireMsg.FailureCode, - ShaOnionBlob: wireMsg.ShaOnionBlob, - DestRef: &channeldb.SettleFailRef{ - Source: chanID, - Height: height, - Index: uint16(i), - }, - } - - // NOTE: UpdateFee is not expected since they are not forwarded. - case *lnwire.UpdateFee: - return nil, fmt.Errorf("unexpected update fee") - - } - - payDescs = append(payDescs, pd) - payDescPtrs = append(payDescPtrs, &payDescs[i]) - } - - return payDescPtrs, nil -} - // commitment represents a commitment to a new state within an active channel. // New commitments can be initiated by either side. Commitments are ordered // into a commitment chain, with one existing for both parties. Each side can @@ -281,8 +195,7 @@ type commitment struct { // new commitment sent to the remote party includes an index in the // shared log which details which of their updates we're including in // this new commitment. - ourMessageIndex uint64 - theirMessageIndex uint64 + messageIndices lntypes.Dual[uint64] // [our|their]HtlcIndex are the current running counters for the HTLCs // offered by either party. This value is incremented each time a party @@ -327,11 +240,15 @@ type commitment struct { // outgoingHTLCs is a slice of all the outgoing HTLC's (from our PoV) // on this commitment transaction. - outgoingHTLCs []PaymentDescriptor + outgoingHTLCs []paymentDescriptor // incomingHTLCs is a slice of all the incoming HTLC's (from our PoV) // on this commitment transaction. - incomingHTLCs []PaymentDescriptor + incomingHTLCs []paymentDescriptor + + // customBlob stores opaque bytes that may be used by custom channels + // to store extra data for a given commitment state. + customBlob fn.Option[tlv.Blob] // [outgoing|incoming]HTLCIndex is an index that maps an output index // on the commitment transaction to the payment descriptor that @@ -343,8 +260,8 @@ type commitment struct { // this map in order to locate the details needed to validate an HTLC // signature while iterating of the outputs in the local commitment // view. - outgoingHTLCIndex map[int32]*PaymentDescriptor - incomingHTLCIndex map[int32]*PaymentDescriptor + outgoingHTLCIndex map[int32]*paymentDescriptor + incomingHTLCIndex map[int32]*paymentDescriptor } // locateOutputIndex is a small helper function to locate the output index of a @@ -352,7 +269,7 @@ type commitment struct { // passed in is to be retained for each output within the commitment // transition. This ensures that we don't assign multiple HTLCs to the same // index within the commitment transaction. -func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, +func locateOutputIndex(p *paymentDescriptor, tx *wire.MsgTx, whoseCommit lntypes.ChannelParty, dups map[PaymentHash][]int32, cltvs []uint32) (int32, error) { @@ -392,7 +309,7 @@ func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, // populateHtlcIndexes modifies the set of HTLCs locked-into the target view // to have full indexing information populated. This information is required as // we need to keep track of the indexes of each HTLC in order to properly write -// the current state to disk, and also to locate the PaymentDescriptor +// the current state to disk, and also to locate the paymentDescriptor // corresponding to HTLC outputs in the commitment transaction. func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType, cltvs []uint32) error { @@ -402,12 +319,12 @@ func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType, // must keep this index so we can validate the HTLC signatures sent to // us. dups := make(map[PaymentHash][]int32) - c.outgoingHTLCIndex = make(map[int32]*PaymentDescriptor) - c.incomingHTLCIndex = make(map[int32]*PaymentDescriptor) + c.outgoingHTLCIndex = make(map[int32]*paymentDescriptor) + c.incomingHTLCIndex = make(map[int32]*paymentDescriptor) // populateIndex is a helper function that populates the necessary // indexes within the commitment view for a particular HTLC. - populateIndex := func(htlc *PaymentDescriptor, incoming bool) error { + populateIndex := func(htlc *paymentDescriptor, incoming bool) error { isDust := HtlcIsDust( chanType, incoming, c.whoseCommit, c.feePerKw, htlc.Amount.ToSatoshis(), c.dustLimit, @@ -495,9 +412,9 @@ func (c *commitment) toDiskCommit( commit := &channeldb.ChannelCommitment{ CommitHeight: c.height, - LocalLogIndex: c.ourMessageIndex, + LocalLogIndex: c.messageIndices.Local, LocalHtlcIndex: c.ourHtlcIndex, - RemoteLogIndex: c.theirMessageIndex, + RemoteLogIndex: c.messageIndices.Remote, RemoteHtlcIndex: c.theirHtlcIndex, LocalBalance: c.ourBalance, RemoteBalance: c.theirBalance, @@ -506,6 +423,7 @@ func (c *commitment) toDiskCommit( CommitTx: c.txn, CommitSig: c.sig, Htlcs: make([]channeldb.HTLC, 0, numHtlcs), + CustomBlob: c.customBlob, } for _, htlc := range c.outgoingHTLCs { @@ -522,9 +440,10 @@ func (c *commitment) toDiskCommit( HtlcIndex: htlc.HtlcIndex, LogIndex: htlc.LogIndex, Incoming: false, + OnionBlob: htlc.OnionBlob, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } - copy(h.OnionBlob[:], htlc.OnionBlob) if whoseCommit.IsLocal() && htlc.sig != nil { h.Signature = htlc.sig.Serialize() @@ -547,9 +466,10 @@ func (c *commitment) toDiskCommit( HtlcIndex: htlc.HtlcIndex, LogIndex: htlc.LogIndex, Incoming: true, + OnionBlob: htlc.OnionBlob, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } - copy(h.OnionBlob[:], htlc.OnionBlob) if whoseCommit.IsLocal() && htlc.sig != nil { h.Signature = htlc.sig.Serialize() } @@ -566,17 +486,17 @@ func (c *commitment) toDiskCommit( // restore commitment state written to disk back into memory once we need to // restart a channel session. func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, - htlc *channeldb.HTLC, localCommitKeys *CommitmentKeyRing, - remoteCommitKeys *CommitmentKeyRing, whoseCommit lntypes.ChannelParty, -) (PaymentDescriptor, error) { + htlc *channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing], + whoseCommit lntypes.ChannelParty, + auxLeaf input.AuxTapLeaf) (paymentDescriptor, error) { - // The proper pkScripts for this PaymentDescriptor must be + // The proper pkScripts for this paymentDescriptor must be // generated so we can easily locate them within the commitment // transaction in the future. var ( ourP2WSH, theirP2WSH []byte ourWitnessScript, theirWitnessScript []byte - pd PaymentDescriptor + pd paymentDescriptor chanType = lc.channelState.ChanType ) @@ -589,10 +509,12 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, chanType, htlc.Incoming, lntypes.Local, feeRate, htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit, ) + localCommitKeys := commitKeys.GetForParty(lntypes.Local) if !isDustLocal && localCommitKeys != nil { scriptInfo, err := genHtlcScript( chanType, htlc.Incoming, lntypes.Local, htlc.RefundTimeout, htlc.RHash, localCommitKeys, + auxLeaf, ) if err != nil { return pd, err @@ -604,10 +526,12 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, chanType, htlc.Incoming, lntypes.Remote, feeRate, htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit, ) + remoteCommitKeys := commitKeys.GetForParty(lntypes.Remote) if !isDustRemote && remoteCommitKeys != nil { scriptInfo, err := genHtlcScript( chanType, htlc.Incoming, lntypes.Remote, htlc.RefundTimeout, htlc.RHash, remoteCommitKeys, + auxLeaf, ) if err != nil { return pd, err @@ -632,14 +556,15 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, // With the scripts reconstructed (depending on if this is our commit // vs theirs or a pending commit for the remote party), we can now // re-create the original payment descriptor. - return PaymentDescriptor{ + return paymentDescriptor{ + ChanID: lc.ChannelID(), RHash: htlc.RHash, Timeout: htlc.RefundTimeout, Amount: htlc.Amt, EntryType: Add, HtlcIndex: htlc.HtlcIndex, LogIndex: htlc.LogIndex, - OnionBlob: htlc.OnionBlob[:], + OnionBlob: htlc.OnionBlob, localOutputIndex: localOutputIndex, remoteOutputIndex: remoteOutputIndex, ourPkScript: ourP2WSH, @@ -647,6 +572,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, theirPkScript: theirP2WSH, theirWitnessScript: theirWitnessScript, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), }, nil } @@ -655,17 +581,18 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, // these payment descriptors can be re-inserted into the in-memory updateLog // for each side. func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight, - htlcs []channeldb.HTLC, localCommitKeys *CommitmentKeyRing, - remoteCommitKeys *CommitmentKeyRing, whoseCommit lntypes.ChannelParty, -) ([]PaymentDescriptor, []PaymentDescriptor, error) { + htlcs []channeldb.HTLC, commitKeys lntypes.Dual[*CommitmentKeyRing], + whoseCommit lntypes.ChannelParty, + auxLeaves fn.Option[CommitAuxLeaves]) ([]paymentDescriptor, + []paymentDescriptor, error) { var ( - incomingHtlcs []PaymentDescriptor - outgoingHtlcs []PaymentDescriptor + incomingHtlcs []paymentDescriptor + outgoingHtlcs []paymentDescriptor ) // For each included HTLC within this commitment state, we'll convert - // the disk format into our in memory PaymentDescriptor format, + // the disk format into our in memory paymentDescriptor format, // partitioning based on if we offered or received the HTLC. for _, htlc := range htlcs { // TODO(roasbeef): set isForwarded to false for all? need to @@ -674,10 +601,19 @@ func (lc *LightningChannel) extractPayDescs(feeRate chainfee.SatPerKWeight, htlc := htlc + auxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.OutgoingHtlcLeaves + if htlc.Incoming { + leaves = l.IncomingHtlcLeaves + } + + return leaves[htlc.HtlcIndex].AuxTapLeaf + }, + )(auxLeaves) + payDesc, err := lc.diskHtlcToPayDesc( - feeRate, &htlc, - localCommitKeys, remoteCommitKeys, - whoseCommit, + feeRate, &htlc, commitKeys, whoseCommit, auxLeaf, ) if err != nil { return incomingHtlcs, outgoingHtlcs, err @@ -706,53 +642,71 @@ func (lc *LightningChannel) diskCommitToMemCommit( // (we extended but weren't able to complete the commitment dance // before shutdown), then the localCommitPoint won't be set as we // haven't yet received a responding commitment from the remote party. - var localCommitKeys, remoteCommitKeys *CommitmentKeyRing + var commitKeys lntypes.Dual[*CommitmentKeyRing] if localCommitPoint != nil { - localCommitKeys = DeriveCommitmentKeys( + commitKeys.SetForParty(lntypes.Local, DeriveCommitmentKeys( localCommitPoint, lntypes.Local, lc.channelState.ChanType, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, - ) + )) } if remoteCommitPoint != nil { - remoteCommitKeys = DeriveCommitmentKeys( + commitKeys.SetForParty(lntypes.Remote, DeriveCommitmentKeys( remoteCommitPoint, lntypes.Remote, lc.channelState.ChanType, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, - ) + )) + } + + auxResult, err := fn.MapOptionZ( + lc.leafStore, + func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(lc.channelState), *diskCommit, + *commitKeys.GetForParty(whoseCommit), + whoseCommit, + ) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) } // With the key rings re-created, we'll now convert all the on-disk - // HTLC"s into PaymentDescriptor's so we can re-insert them into our + // HTLC"s into paymentDescriptor's so we can re-insert them into our // update log. incomingHtlcs, outgoingHtlcs, err := lc.extractPayDescs( chainfee.SatPerKWeight(diskCommit.FeePerKw), - diskCommit.Htlcs, localCommitKeys, remoteCommitKeys, - whoseCommit, + diskCommit.Htlcs, commitKeys, whoseCommit, auxResult.AuxLeaves, ) if err != nil { return nil, err } + messageIndices := lntypes.Dual[uint64]{ + Local: diskCommit.LocalLogIndex, + Remote: diskCommit.RemoteLogIndex, + } + // With the necessary items generated, we'll now re-construct the // commitment state as it was originally present in memory. commit := &commitment{ - height: diskCommit.CommitHeight, - whoseCommit: whoseCommit, - ourBalance: diskCommit.LocalBalance, - theirBalance: diskCommit.RemoteBalance, - ourMessageIndex: diskCommit.LocalLogIndex, - ourHtlcIndex: diskCommit.LocalHtlcIndex, - theirMessageIndex: diskCommit.RemoteLogIndex, - theirHtlcIndex: diskCommit.RemoteHtlcIndex, - txn: diskCommit.CommitTx, - sig: diskCommit.CommitSig, - fee: diskCommit.CommitFee, - feePerKw: chainfee.SatPerKWeight(diskCommit.FeePerKw), - incomingHTLCs: incomingHtlcs, - outgoingHTLCs: outgoingHtlcs, + height: diskCommit.CommitHeight, + whoseCommit: whoseCommit, + ourBalance: diskCommit.LocalBalance, + theirBalance: diskCommit.RemoteBalance, + messageIndices: messageIndices, + ourHtlcIndex: diskCommit.LocalHtlcIndex, + theirHtlcIndex: diskCommit.RemoteHtlcIndex, + txn: diskCommit.CommitTx, + sig: diskCommit.CommitSig, + fee: diskCommit.CommitFee, + feePerKw: chainfee.SatPerKWeight(diskCommit.FeePerKw), + incomingHTLCs: incomingHtlcs, + outgoingHTLCs: outgoingHtlcs, + customBlob: diskCommit.CustomBlob, } if whoseCommit.IsLocal() { commit.dustLimit = lc.channelState.LocalChanCfg.DustLimit @@ -797,6 +751,10 @@ type LightningChannel struct { // machine. Signer input.Signer + // leafStore is used to retrieve extra tapscript leaves for special + // custom channel types. + leafStore fn.Option[AuxLeafStore] + // signDesc is the primary sign descriptor that is capable of signing // the commitment transaction that spends the multi-sig output. signDesc *input.SignDescriptor @@ -809,6 +767,14 @@ type LightningChannel struct { // signatures, of which there may be hundreds. sigPool *SigPool + // auxSigner is a special signer used to obtain opaque signatures for + // 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 @@ -817,15 +783,13 @@ type LightningChannel struct { // accepted. currentHeight uint64 - // remoteCommitChain is the remote node's commitment chain. Any new - // commitments we initiate are added to the tip of this chain. - remoteCommitChain *commitmentChain - - // localCommitChain is our local commitment chain. Any new commitments - // received are added to the tip of this chain. The tail (or lowest - // height) in this chain is our current accepted state, which we are - // able to broadcast safely. - localCommitChain *commitmentChain + // commitChains is a Dual of the local and remote node's commitment + // chains. Any new commitments we initiate are added to Remote chain's + // tip. The Local portion of this field is our local commitment chain. + // Any new commitments received are added to the tip of this chain. + // The tail (or lowest height) in this chain is our current accepted + // state, which we are able to broadcast safely. + commitChains lntypes.Dual[*commitmentChain] channelState *channeldb.OpenChannel @@ -835,8 +799,7 @@ type LightningChannel struct { // updates to this channel. The log is walked backwards as HTLC updates // are applied in order to re-construct a commitment transaction from a // commitment. The log is compacted once a revocation is received. - localUpdateLog *updateLog - remoteUpdateLog *updateLog + updateLogs lntypes.Dual[*updateLog] // log is a channel-specific logging instance. log btclog.Logger @@ -872,6 +835,10 @@ type channelOpts struct { localNonce *musig2.Nonces remoteNonce *musig2.Nonces + leafStore fn.Option[AuxLeafStore] + auxSigner fn.Option[AuxSigner] + auxResolver fn.Option[AuxContractResolver] + skipNonceInit bool } @@ -902,6 +869,28 @@ func WithSkipNonceInit() ChannelOpt { } } +// WithLeafStore is used to specify a custom leaf store for the channel. +func WithLeafStore(store AuxLeafStore) ChannelOpt { + return func(o *channelOpts) { + o.leafStore = fn.Some[AuxLeafStore](store) + } +} + +// WithAuxSigner is used to specify a custom aux signer for the channel. +func WithAuxSigner(signer AuxSigner) ChannelOpt { + return func(o *channelOpts) { + o.auxSigner = fn.Some[AuxSigner](signer) + } +} + +// 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{} @@ -932,6 +921,10 @@ func NewLightningChannel(signer input.Signer, remoteUpdateLog := newUpdateLog( localCommit.RemoteLogIndex, localCommit.RemoteHtlcIndex, ) + updateLogs := lntypes.Dual[*updateLog]{ + Local: localUpdateLog, + Remote: remoteUpdateLog, + } logPrefix := fmt.Sprintf("ChannelPoint(%v):", state.FundingOutpoint) @@ -942,16 +935,24 @@ func NewLightningChannel(signer input.Signer, return nil, fmt.Errorf("unable to derive shachain: %w", err) } + commitChains := lntypes.Dual[*commitmentChain]{ + Local: newCommitmentChain(), + Remote: newCommitmentChain(), + } + lc := &LightningChannel{ - Signer: signer, - sigPool: sigPool, - currentHeight: localCommit.CommitHeight, - remoteCommitChain: newCommitmentChain(), - localCommitChain: newCommitmentChain(), - channelState: state, - commitBuilder: NewCommitmentBuilder(state), - localUpdateLog: localUpdateLog, - remoteUpdateLog: remoteUpdateLog, + Signer: signer, + leafStore: opts.leafStore, + auxSigner: opts.auxSigner, + auxResolver: opts.auxResolver, + sigPool: sigPool, + currentHeight: localCommit.CommitHeight, + commitChains: commitChains, + channelState: state, + commitBuilder: NewCommitmentBuilder( + state, opts.leafStore, + ), + updateLogs: updateLogs, Capacity: state.Capacity, taprootNonceProducer: taprootNonceProducer, log: build.NewPrefixLog(logPrefix, walletLog), @@ -1013,6 +1014,7 @@ func (lc *LightningChannel) createSignDesc() error { if chanState.ChanType.IsTaproot() { fundingPkScript, _, err = input.GenTaprootFundingScript( localKey, remoteKey, int64(lc.channelState.Capacity), + chanState.TapscriptRoot, ) if err != nil { return err @@ -1056,7 +1058,7 @@ func (lc *LightningChannel) ResetState() { lc.Unlock() } -// logUpdateToPayDesc converts a LogUpdate into a matching PaymentDescriptor +// logUpdateToPayDesc converts a LogUpdate into a matching paymentDescriptor // entry that can be re-inserted into the update log. This method is used when // we extended a state to the remote party, but the connection was obstructed // before we could finish the commitment dance. In this case, we need to @@ -1065,25 +1067,27 @@ func (lc *LightningChannel) ResetState() { func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, remoteUpdateLog *updateLog, commitHeight uint64, feeRate chainfee.SatPerKWeight, remoteCommitKeys *CommitmentKeyRing, - remoteDustLimit btcutil.Amount) (*PaymentDescriptor, error) { + remoteDustLimit btcutil.Amount, + auxLeaves fn.Option[CommitAuxLeaves]) (*paymentDescriptor, error) { // Depending on the type of update message we'll map that to a distinct - // PaymentDescriptor instance. - var pd *PaymentDescriptor + // paymentDescriptor instance. + var pd *paymentDescriptor switch wireMsg := logUpdate.UpdateMsg.(type) { - // For offered HTLC's, we'll map that to a PaymentDescriptor with the + // For offered HTLC's, we'll map that to a paymentDescriptor with the // type Add, ensuring we restore the necessary fields. From the PoV of // the commitment chain, this HTLC was included in the remote chain, // but not the local chain. case *lnwire.UpdateAddHTLC: // First, we'll map all the relevant fields in the // UpdateAddHTLC message to their corresponding fields in the - // PaymentDescriptor struct. We also set addCommitHeightRemote + // paymentDescriptor struct. We also set addCommitHeightRemote // as we've included this HTLC in our local commitment chain // for the remote party. - pd = &PaymentDescriptor{ + pd = &paymentDescriptor{ + ChanID: wireMsg.ChanID, RHash: wireMsg.PaymentHash, Timeout: wireMsg.Expiry, Amount: wireMsg.Amount, @@ -1091,20 +1095,27 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, HtlcIndex: wireMsg.ID, LogIndex: logUpdate.LogIndex, addCommitHeightRemote: commitHeight, + OnionBlob: wireMsg.OnionBlob, BlindingPoint: wireMsg.BlindingPoint, + CustomRecords: wireMsg.CustomRecords.Copy(), } - pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob)) - copy(pd.OnionBlob[:], wireMsg.OnionBlob[:]) isDustRemote := HtlcIsDust( lc.channelState.ChanType, false, lntypes.Remote, feeRate, wireMsg.Amount.ToSatoshis(), remoteDustLimit, ) if !isDustRemote { + auxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.OutgoingHtlcLeaves + return leaves[pd.HtlcIndex].AuxTapLeaf + }, + )(auxLeaves) + scriptInfo, err := genHtlcScript( lc.channelState.ChanType, false, lntypes.Remote, wireMsg.Expiry, wireMsg.PaymentHash, - remoteCommitKeys, + remoteCommitKeys, auxLeaf, ) if err != nil { return nil, err @@ -1116,11 +1127,12 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, // For HTLC's we're offered we'll fetch the original offered HTLC // from the remote party's update log so we can retrieve the same - // PaymentDescriptor that SettleHTLC would produce. + // paymentDescriptor that SettleHTLC would produce. case *lnwire.UpdateFulfillHTLC: ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) - pd = &PaymentDescriptor{ + pd = &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, RPreimage: wireMsg.PaymentPreimage, @@ -1137,7 +1149,8 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, case *lnwire.UpdateFailHTLC: ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) - pd = &PaymentDescriptor{ + pd = &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1153,7 +1166,8 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) // TODO(roasbeef): err if nil? - pd = &PaymentDescriptor{ + pd = &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1171,7 +1185,8 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, // height to the same value, as we consider the fee update locked in by // adding and removing it at the same height. case *lnwire.UpdateFee: - pd = &PaymentDescriptor{ + pd = &paymentDescriptor{ + ChanID: wireMsg.ChanID, LogIndex: logUpdate.LogIndex, Amount: lnwire.NewMSatFromSatoshis( btcutil.Amount(wireMsg.FeePerKw), @@ -1185,29 +1200,30 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, return pd, nil } -// localLogUpdateToPayDesc converts a LogUpdate into a matching PaymentDescriptor -// entry that can be re-inserted into the local update log. This method is used -// when we sent an update+sig, receive a revocation, but drop right before the -// counterparty can sign for the update we just sent. In this case, we need to -// re-insert the original entries back into the update log so we'll be expecting -// the peer to sign them. The height of the remote commitment is expected to be -// provided and we restore all log update entries with this height, even though -// the real height may be lower. In the way these fields are used elsewhere, this -// doesn't change anything. +// localLogUpdateToPayDesc converts a LogUpdate into a matching +// paymentDescriptor entry that can be re-inserted into the local update log. +// This method is used when we sent an update+sig, receive a revocation, but +// drop right before the counterparty can sign for the update we just sent. In +// this case, we need to re-insert the original entries back into the update +// log so we'll be expecting the peer to sign them. The height of the remote +// commitment is expected to be provided and we restore all log update entries +// with this height, even though the real height may be lower. In the way these +// fields are used elsewhere, this doesn't change anything. func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpdate, - remoteUpdateLog *updateLog, commitHeight uint64) (*PaymentDescriptor, + remoteUpdateLog *updateLog, commitHeight uint64) (*paymentDescriptor, error) { // Since Add updates aren't saved to disk under this key, the update will // never be an Add. switch wireMsg := logUpdate.UpdateMsg.(type) { // For HTLCs that we settled, we'll fetch the original offered HTLC from - // the remote update log so we can retrieve the same PaymentDescriptor that - // ReceiveHTLCSettle would produce. + // the remote update log so we can retrieve the same paymentDescriptor + // that ReceiveHTLCSettle would produce. case *lnwire.UpdateFulfillHTLC: ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, RPreimage: wireMsg.PaymentPreimage, @@ -1223,7 +1239,8 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda case *lnwire.UpdateFailHTLC: ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1238,7 +1255,8 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda case *lnwire.UpdateFailMalformedHTLC: ogHTLC := remoteUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1250,7 +1268,8 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda }, nil case *lnwire.UpdateFee: - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, LogIndex: logUpdate.LogIndex, Amount: lnwire.NewMSatFromSatoshis( btcutil.Amount(wireMsg.FeePerKw), @@ -1266,7 +1285,7 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda } // remoteLogUpdateToPayDesc converts a LogUpdate into a matching -// PaymentDescriptor entry that can be re-inserted into the update log. This +// paymentDescriptor entry that can be re-inserted into the update log. This // method is used when we revoked a local commitment, but the connection was // obstructed before we could sign a remote commitment that contains these // updates. In this case, we need to re-insert the original entries back into @@ -1276,12 +1295,13 @@ func (lc *LightningChannel) localLogUpdateToPayDesc(logUpdate *channeldb.LogUpda // may be lower. In the way these fields are used elsewhere, this doesn't change // anything. func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpdate, - localUpdateLog *updateLog, commitHeight uint64) (*PaymentDescriptor, + localUpdateLog *updateLog, commitHeight uint64) (*paymentDescriptor, error) { switch wireMsg := logUpdate.UpdateMsg.(type) { case *lnwire.UpdateAddHTLC: - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: wireMsg.ChanID, RHash: wireMsg.PaymentHash, Timeout: wireMsg.Expiry, Amount: wireMsg.Amount, @@ -1289,10 +1309,10 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd HtlcIndex: wireMsg.ID, LogIndex: logUpdate.LogIndex, addCommitHeightLocal: commitHeight, + OnionBlob: wireMsg.OnionBlob, BlindingPoint: wireMsg.BlindingPoint, + CustomRecords: wireMsg.CustomRecords.Copy(), } - pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob)) - copy(pd.OnionBlob, wireMsg.OnionBlob[:]) // We don't need to generate an htlc script yet. This will be // done once we sign our remote commitment. @@ -1301,11 +1321,12 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd // For HTLCs that the remote party settled, we'll fetch the original // offered HTLC from the local update log so we can retrieve the same - // PaymentDescriptor that ReceiveHTLCSettle would produce. + // paymentDescriptor that ReceiveHTLCSettle would produce. case *lnwire.UpdateFulfillHTLC: ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, RPreimage: wireMsg.PaymentPreimage, @@ -1321,7 +1342,8 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd case *lnwire.UpdateFailHTLC: ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1336,7 +1358,8 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd case *lnwire.UpdateFailMalformedHTLC: ogHTLC := localUpdateLog.lookupHtlc(wireMsg.ID) - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, Amount: ogHTLC.Amount, RHash: ogHTLC.RHash, ParentIndex: ogHTLC.HtlcIndex, @@ -1354,7 +1377,8 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd // height to the same value, as we consider the fee update locked in by // adding and removing it at the same height. case *lnwire.UpdateFee: - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: wireMsg.ChanID, LogIndex: logUpdate.LogIndex, Amount: lnwire.NewMSatFromSatoshis( btcutil.Amount(wireMsg.FeePerKw), @@ -1401,10 +1425,10 @@ func (lc *LightningChannel) restoreCommitState( if err != nil { return err } - lc.localCommitChain.addCommitment(localCommit) + lc.commitChains.Local.addCommitment(localCommit) lc.log.Tracef("starting local commitment: %v", - lnutils.SpewLogClosure(lc.localCommitChain.tail())) + lnutils.SpewLogClosure(lc.commitChains.Local.tail())) // We'll also do the same for the remote commitment chain. remoteCommit, err := lc.diskCommitToMemCommit( @@ -1414,10 +1438,10 @@ func (lc *LightningChannel) restoreCommitState( if err != nil { return err } - lc.remoteCommitChain.addCommitment(remoteCommit) + lc.commitChains.Remote.addCommitment(remoteCommit) lc.log.Tracef("starting remote commitment: %v", - lnutils.SpewLogClosure(lc.remoteCommitChain.tail())) + lnutils.SpewLogClosure(lc.commitChains.Remote.tail())) var ( pendingRemoteCommit *commitment @@ -1446,10 +1470,10 @@ func (lc *LightningChannel) restoreCommitState( if err != nil { return err } - lc.remoteCommitChain.addCommitment(pendingRemoteCommit) + lc.commitChains.Remote.addCommitment(pendingRemoteCommit) lc.log.Debugf("pending remote commitment: %v", - lnutils.SpewLogClosure(lc.remoteCommitChain.tip())) + lnutils.SpewLogClosure(lc.commitChains.Remote.tip())) // We'll also re-create the set of commitment keys needed to // fully re-derive the state. @@ -1598,7 +1622,7 @@ func (lc *LightningChannel) restoreStateLogs( htlc.addCommitHeightRemote = incomingRemoteAddHeights[htlc.HtlcIndex] // Restore the htlc back to the remote log. - lc.remoteUpdateLog.restoreHtlc(&htlc) + lc.updateLogs.Remote.restoreHtlc(&htlc) } // Similarly, we'll do the same for the outgoing HTLCs within the @@ -1613,7 +1637,7 @@ func (lc *LightningChannel) restoreStateLogs( htlc.addCommitHeightLocal = outgoingLocalAddHeights[htlc.HtlcIndex] // Restore the htlc back to the local log. - lc.localUpdateLog.restoreHtlc(&htlc) + lc.updateLogs.Local.restoreHtlc(&htlc) } // If we have a dangling (un-acked) commit for the remote party, then we @@ -1658,7 +1682,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( logUpdate := logUpdate payDesc, err := lc.remoteLogUpdateToPayDesc( - &logUpdate, lc.localUpdateLog, localCommitmentHeight, + &logUpdate, lc.updateLogs.Local, localCommitmentHeight, ) if err != nil { return err @@ -1668,7 +1692,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( // Sanity check that we are not restoring a remote log update // that we haven't received a sig for. - if logIdx >= lc.remoteUpdateLog.logIndex { + if logIdx >= lc.updateLogs.Remote.logIndex { return fmt.Errorf("attempted to restore an "+ "unsigned remote update: log_index=%v", logIdx) @@ -1692,7 +1716,7 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( // height as this commitment will include these updates for // their new remote commitment. if pendingRemoteCommit != nil { - if logIdx < pendingRemoteCommit.theirMessageIndex { + if logIdx < pendingRemoteCommit.messageIndices.Remote { height = pendingRemoteCommit.height heightSet = true } @@ -1709,15 +1733,17 @@ func (lc *LightningChannel) restorePendingRemoteUpdates( payDesc.removeCommitHeightRemote = height } - lc.remoteUpdateLog.restoreUpdate(payDesc) + lc.updateLogs.Remote.restoreUpdate(payDesc) default: if heightSet { payDesc.removeCommitHeightRemote = height } - lc.remoteUpdateLog.restoreUpdate(payDesc) - lc.localUpdateLog.markHtlcModified(payDesc.ParentIndex) + lc.updateLogs.Remote.restoreUpdate(payDesc) + lc.updateLogs.Local.markHtlcModified( + payDesc.ParentIndex, + ) } } @@ -1736,20 +1762,23 @@ func (lc *LightningChannel) restorePeerLocalUpdates(updates []channeldb.LogUpdat logUpdate := logUpdate payDesc, err := lc.localLogUpdateToPayDesc( - &logUpdate, lc.remoteUpdateLog, remoteCommitmentHeight, + &logUpdate, lc.updateLogs.Remote, + remoteCommitmentHeight, ) if err != nil { return err } - lc.localUpdateLog.restoreUpdate(payDesc) + lc.updateLogs.Local.restoreUpdate(payDesc) // Since Add updates are not stored and FeeUpdates don't have a // corresponding entry in the remote update log, we only need to // mark the htlc as modified if the update was Settle, Fail, or // MalformedFail. if payDesc.EntryType != FeeUpdate { - lc.remoteUpdateLog.markHtlcModified(payDesc.ParentIndex) + lc.updateLogs.Remote.markHtlcModified( + payDesc.ParentIndex, + ) } } @@ -1765,16 +1794,30 @@ func (lc *LightningChannel) restorePendingLocalUpdates( pendingCommit := pendingRemoteCommitDiff.Commitment pendingHeight := pendingCommit.CommitHeight + auxResult, err := fn.MapOptionZ( + lc.leafStore, + func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(lc.channelState), pendingCommit, + *pendingRemoteKeys, lntypes.Remote, + ) + }, + ).Unpack() + if err != nil { + return fmt.Errorf("unable to fetch aux leaves: %w", err) + } + // If we did have a dangling commit, then we'll examine which updates // we included in that state and re-insert them into our update log. for _, logUpdate := range pendingRemoteCommitDiff.LogUpdates { logUpdate := logUpdate payDesc, err := lc.logUpdateToPayDesc( - &logUpdate, lc.remoteUpdateLog, pendingHeight, + &logUpdate, lc.updateLogs.Remote, pendingHeight, chainfee.SatPerKWeight(pendingCommit.FeePerKw), pendingRemoteKeys, lc.channelState.RemoteChanCfg.DustLimit, + auxResult.AuxLeaves, ) if err != nil { return err @@ -1784,9 +1827,9 @@ func (lc *LightningChannel) restorePendingLocalUpdates( // updates, so they will be unset. To account for this we set // them to to current update log index. if payDesc.EntryType == FeeUpdate && payDesc.LogIndex == 0 && - lc.localUpdateLog.logIndex > 0 { + lc.updateLogs.Local.logIndex > 0 { - payDesc.LogIndex = lc.localUpdateLog.logIndex + payDesc.LogIndex = lc.updateLogs.Local.logIndex lc.log.Debugf("Found FeeUpdate on "+ "pendingRemoteCommitDiff without logIndex, "+ "using %v", payDesc.LogIndex) @@ -1794,10 +1837,10 @@ func (lc *LightningChannel) restorePendingLocalUpdates( // At this point the restored update's logIndex must be equal // to the update log, otherwise something is horribly wrong. - if payDesc.LogIndex != lc.localUpdateLog.logIndex { + if payDesc.LogIndex != lc.updateLogs.Local.logIndex { panic(fmt.Sprintf("log index mismatch: "+ "%v vs %v", payDesc.LogIndex, - lc.localUpdateLog.logIndex)) + lc.updateLogs.Local.logIndex)) } switch payDesc.EntryType { @@ -1807,21 +1850,25 @@ func (lc *LightningChannel) restorePendingLocalUpdates( // panic to catch this. // TODO(halseth): remove when cause of htlc entry bug // is found. - if payDesc.HtlcIndex != lc.localUpdateLog.htlcCounter { + if payDesc.HtlcIndex != + lc.updateLogs.Local.htlcCounter { + panic(fmt.Sprintf("htlc index mismatch: "+ "%v vs %v", payDesc.HtlcIndex, - lc.localUpdateLog.htlcCounter)) + lc.updateLogs.Local.htlcCounter)) } - lc.localUpdateLog.appendHtlc(payDesc) + lc.updateLogs.Local.appendHtlc(payDesc) case FeeUpdate: - lc.localUpdateLog.appendUpdate(payDesc) + lc.updateLogs.Local.appendUpdate(payDesc) default: - lc.localUpdateLog.appendUpdate(payDesc) + lc.updateLogs.Local.appendUpdate(payDesc) - lc.remoteUpdateLog.markHtlcModified(payDesc.ParentIndex) + lc.updateLogs.Remote.markHtlcModified( + payDesc.ParentIndex, + ) } } @@ -1858,6 +1905,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 @@ -1927,6 +1978,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 @@ -1937,7 +1996,10 @@ type BreachRetribution struct { // required to construct the BreachRetribution. If the revocation log is missing // the required fields then ErrRevLogDataMissing will be returned. func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, - breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) { + breachHeight uint32, spendTx *wire.MsgTx, + 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 @@ -1980,21 +2042,40 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, leaseExpiry = chanState.ThawHeight } + auxResult, err := fn.MapOptionZ( + leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromRevocation(revokedLog) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) + } + // Since it is the remote breach we are reconstructing, the output // going to us will be a to-remote script with our local params. + remoteAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.RemoteAuxLeaf + }, + )(auxResult.AuxLeaves) isRemoteInitiator := !chanState.IsInitiator ourScript, ourDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, remoteAuxLeaf, ) if err != nil { return nil, err } + localAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.LocalAuxLeaf + }, + )(auxResult.AuxLeaves) theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) theirScript, err := CommitScriptToSelf( chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, - keyRing.RevocationKey, theirDelay, leaseExpiry, + keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf, ) if err != nil { return nil, err @@ -2012,7 +2093,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, if revokedLog != nil { br, ourAmt, theirAmt, err = createBreachRetribution( revokedLog, spendTx, chanState, keyRing, - commitmentSecret, leaseExpiry, + commitmentSecret, leaseExpiry, auxResult.AuxLeaves, ) if err != nil { return nil, err @@ -2081,6 +2162,39 @@ 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, + ChanType: chanState.ChanType, + 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, @@ -2128,6 +2242,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, + ChanType: chanState.ChanType, + 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 @@ -2146,7 +2292,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, func createHtlcRetribution(chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitHash chainhash.Hash, commitmentSecret *btcec.PrivateKey, leaseExpiry uint32, - htlc *channeldb.HTLCEntry) (HtlcRetribution, error) { + htlc *channeldb.HTLCEntry, + auxLeaves fn.Option[CommitAuxLeaves]) (HtlcRetribution, error) { var emptyRetribution HtlcRetribution @@ -2156,10 +2303,24 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, // We'll generate the original second level witness script now, as // we'll need it if we're revoking an HTLC output on the remote // commitment transaction, and *they* go to the second level. + secondLevelAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] { + return fn.MapOption(func(val uint16) input.AuxTapLeaf { + idx := input.HtlcIndex(val) + + if htlc.Incoming.Val { + leaves := l.IncomingHtlcLeaves[idx] + return leaves.SecondLevelLeaf + } + + return l.OutgoingHtlcLeaves[idx].SecondLevelLeaf + })(htlc.HtlcIndex.ValOpt()) + }, + )(auxLeaves) secondLevelScript, err := SecondLevelHtlcScript( chanState.ChanType, isRemoteInitiator, keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, - leaseExpiry, + leaseExpiry, fn.FlattenOption(secondLevelAuxLeaf), ) if err != nil { return emptyRetribution, err @@ -2170,9 +2331,24 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, // HTLC script. Otherwise, is this was an outgoing HTLC that we sent, // then from the PoV of the remote commitment state, they're the // receiver of this HTLC. + htlcLeaf := fn.ChainOption( + func(l CommitAuxLeaves) fn.Option[input.AuxTapLeaf] { + return fn.MapOption(func(val uint16) input.AuxTapLeaf { + idx := input.HtlcIndex(val) + + if htlc.Incoming.Val { + leaves := l.IncomingHtlcLeaves[idx] + return leaves.AuxTapLeaf + } + + return l.OutgoingHtlcLeaves[idx].AuxTapLeaf + })(htlc.HtlcIndex.ValOpt()) + }, + )(auxLeaves) scriptInfo, err := genHtlcScript( - chanState.ChanType, htlc.Incoming, lntypes.Remote, - htlc.RefundTimeout, htlc.RHash, keyRing, + chanState.ChanType, htlc.Incoming.Val, lntypes.Remote, + htlc.RefundTimeout.Val, htlc.RHash.Val, keyRing, + fn.FlattenOption(htlcLeaf), ) if err != nil { return emptyRetribution, err @@ -2185,7 +2361,7 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, WitnessScript: scriptInfo.WitnessScriptToSign(), Output: &wire.TxOut{ PkScript: scriptInfo.PkScript(), - Value: int64(htlc.Amt), + Value: int64(htlc.Amt.Val.Int()), }, HashType: sweepSigHash(chanState.ChanType), } @@ -2218,10 +2394,10 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, SignDesc: signDesc, OutPoint: wire.OutPoint{ Hash: commitHash, - Index: uint32(htlc.OutputIndex), + Index: uint32(htlc.OutputIndex.Val), }, SecondLevelWitnessScript: secondLevelWitnessScript, - IsIncoming: htlc.Incoming, + IsIncoming: htlc.Incoming.Val, SecondLevelTapTweak: secondLevelTapTweak, }, nil } @@ -2236,7 +2412,9 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel, func createBreachRetribution(revokedLog *channeldb.RevocationLog, spendTx *wire.MsgTx, chanState *channeldb.OpenChannel, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, - leaseExpiry uint32) (*BreachRetribution, int64, int64, error) { + leaseExpiry uint32, + auxLeaves fn.Option[CommitAuxLeaves]) (*BreachRetribution, int64, int64, + error) { commitHash := revokedLog.CommitTxHash @@ -2244,8 +2422,8 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, htlcRetributions := make([]HtlcRetribution, len(revokedLog.HTLCEntries)) for i, htlc := range revokedLog.HTLCEntries { hr, err := createHtlcRetribution( - chanState, keyRing, commitHash, - commitmentSecret, leaseExpiry, htlc, + chanState, keyRing, commitHash.Val, + commitmentSecret, leaseExpiry, htlc, auxLeaves, ) if err != nil { return nil, 0, 0, err @@ -2257,10 +2435,10 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, // Construct the our outpoint. ourOutpoint := wire.OutPoint{ - Hash: commitHash, + Hash: commitHash.Val, } - if revokedLog.OurOutputIndex != channeldb.OutputIndexEmpty { - ourOutpoint.Index = uint32(revokedLog.OurOutputIndex) + if revokedLog.OurOutputIndex.Val != channeldb.OutputIndexEmpty { + ourOutpoint.Index = uint32(revokedLog.OurOutputIndex.Val) // If the spend transaction is provided, then we use it to get // the value of our output. @@ -2283,26 +2461,29 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, // contains our output amount. Due to a previous // migration, this field may be empty in which case an // error will be returned. - if revokedLog.OurBalance == nil { - return nil, 0, 0, ErrRevLogDataMissing + b, err := revokedLog.OurBalance.ValOpt().UnwrapOrErr( + ErrRevLogDataMissing, + ) + if err != nil { + return nil, 0, 0, err } - ourAmt = int64(revokedLog.OurBalance.ToSatoshis()) + ourAmt = int64(b.Int().ToSatoshis()) } } // Construct the their outpoint. theirOutpoint := wire.OutPoint{ - Hash: commitHash, + Hash: commitHash.Val, } - if revokedLog.TheirOutputIndex != channeldb.OutputIndexEmpty { - theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex) + if revokedLog.TheirOutputIndex.Val != channeldb.OutputIndexEmpty { + theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex.Val) // If the spend transaction is provided, then we use it to get // the value of the remote parties' output. if spendTx != nil { // Sanity check that TheirOutputIndex is within range. - if int(revokedLog.TheirOutputIndex) >= + if int(revokedLog.TheirOutputIndex.Val) >= len(spendTx.TxOut) { return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+ @@ -2320,16 +2501,19 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog, // contains remote parties' output amount. Due to a // previous migration, this field may be empty in which // case an error will be returned. - if revokedLog.TheirBalance == nil { - return nil, 0, 0, ErrRevLogDataMissing + b, err := revokedLog.TheirBalance.ValOpt().UnwrapOrErr( + ErrRevLogDataMissing, + ) + if err != nil { + return nil, 0, 0, err } - theirAmt = int64(revokedLog.TheirBalance.ToSatoshis()) + theirAmt = int64(b.Int().ToSatoshis()) } } return &BreachRetribution{ - BreachTxHash: commitHash, + BreachTxHash: commitHash.Val, ChainHash: chanState.ChainHash, LocalOutpoint: ourOutpoint, RemoteOutpoint: theirOutpoint, @@ -2383,16 +2567,15 @@ func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment, continue } - entry := &channeldb.HTLCEntry{ - RHash: htlc.RHash, - RefundTimeout: htlc.RefundTimeout, - OutputIndex: uint16(htlc.OutputIndex), - Incoming: htlc.Incoming, - Amt: htlc.Amt.ToSatoshis(), + entry, err := channeldb.NewHTLCEntryFromHTLC(htlc) + if err != nil { + return nil, 0, 0, err } + hr, err := createHtlcRetribution( chanState, keyRing, commitHash, commitmentSecret, leaseExpiry, entry, + fn.None[CommitAuxLeaves](), ) if err != nil { return nil, 0, 0, err @@ -2458,20 +2641,43 @@ func HtlcIsDust(chanType channeldb.ChannelType, return (htlcAmt - htlcFee) < dustLimit } -// htlcView represents the "active" HTLCs at a particular point within the +// HtlcView represents the "active" HTLCs at a particular point within the // history of the HTLC update log. -type htlcView struct { - ourUpdates []*PaymentDescriptor - theirUpdates []*PaymentDescriptor - feePerKw chainfee.SatPerKWeight +type HtlcView struct { + // NextHeight is the height of the commitment transaction that will be + // created using this view. + NextHeight uint64 + + // OurUpdates are our outgoing HTLCs. + OurUpdates []*paymentDescriptor + + // TheirUpdates are their incoming HTLCs. + TheirUpdates []*paymentDescriptor + + // FeePerKw is the fee rate in sat/kw of the commitment transaction. + FeePerKw chainfee.SatPerKWeight +} + +// AuxOurUpdates returns the outgoing HTLCs as a read-only copy of +// AuxHtlcDescriptors. +func (v *HtlcView) AuxOurUpdates() []AuxHtlcDescriptor { + return fn.Map(newAuxHtlcDescriptor, v.OurUpdates) +} + +// AuxTheirUpdates returns the incoming HTLCs as a read-only copy of +// AuxHtlcDescriptors. +func (v *HtlcView) AuxTheirUpdates() []AuxHtlcDescriptor { + return fn.Map(newAuxHtlcDescriptor, v.TheirUpdates) } // fetchHTLCView returns all the candidate HTLC updates which should be // considered for inclusion within a commitment based on the passed HTLC log // indexes. -func (lc *LightningChannel) fetchHTLCView(theirLogIndex, ourLogIndex uint64) *htlcView { - var ourHTLCs []*PaymentDescriptor - for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() { +func (lc *LightningChannel) fetchHTLCView(theirLogIndex, + ourLogIndex uint64) *HtlcView { + + var ourHTLCs []*paymentDescriptor + for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() { htlc := e.Value // This HTLC is active from this point-of-view iff the log @@ -2482,8 +2688,8 @@ func (lc *LightningChannel) fetchHTLCView(theirLogIndex, ourLogIndex uint64) *ht } } - var theirHTLCs []*PaymentDescriptor - for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() { + var theirHTLCs []*paymentDescriptor + for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() { htlc := e.Value // If this is an incoming HTLC, then it is only active from @@ -2494,9 +2700,9 @@ func (lc *LightningChannel) fetchHTLCView(theirLogIndex, ourLogIndex uint64) *ht } } - return &htlcView{ - ourUpdates: ourHTLCs, - theirUpdates: theirHTLCs, + return &HtlcView{ + OurUpdates: ourHTLCs, + TheirUpdates: theirHTLCs, } } @@ -2511,10 +2717,10 @@ func (lc *LightningChannel) fetchCommitmentView( ourLogIndex, ourHtlcIndex, theirLogIndex, theirHtlcIndex uint64, keyRing *CommitmentKeyRing) (*commitment, error) { - commitChain := lc.localCommitChain + commitChain := lc.commitChains.Local dustLimit := lc.channelState.LocalChanCfg.DustLimit if whoseCommitChain.IsRemote() { - commitChain = lc.remoteCommitChain + commitChain = lc.commitChains.Remote dustLimit = lc.channelState.RemoteChanCfg.DustLimit } @@ -2533,12 +2739,16 @@ func (lc *LightningChannel) fetchCommitmentView( if err != nil { return nil, err } - feePerKw := filteredHTLCView.feePerKw + feePerKw := filteredHTLCView.FeePerKw + + htlcView.NextHeight = nextHeight + filteredHTLCView.NextHeight = nextHeight // Actually generate unsigned commitment transaction for this view. commitTx, err := lc.commitBuilder.createUnsignedCommitmentTx( ourBalance, theirBalance, whoseCommitChain, feePerKw, - nextHeight, filteredHTLCView, keyRing, + nextHeight, htlcView, filteredHTLCView, keyRing, + commitChain.tip(), ) if err != nil { return nil, err @@ -2573,32 +2783,58 @@ func (lc *LightningChannel) fetchCommitmentView( effFeeRate, spew.Sdump(commitTx)) } + // Given the custom blob of the past state, and this new HTLC view, + // we'll generate a new blob for the latest commitment. + newCommitBlob, err := fn.MapOptionZ( + lc.leafStore, + func(s AuxLeafStore) fn.Result[fn.Option[tlv.Blob]] { + return updateAuxBlob( + s, lc.channelState, + commitChain.tip().customBlob, htlcView, + whoseCommitChain, ourBalance, theirBalance, + *keyRing, + ) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) + } + + messageIndices := lntypes.Dual[uint64]{ + Local: ourLogIndex, + Remote: theirLogIndex, + } + // With the commitment view created, store the resulting balances and // transaction with the other parameters for this height. c := &commitment{ - ourBalance: commitTx.ourBalance, - theirBalance: commitTx.theirBalance, - txn: commitTx.txn, - fee: commitTx.fee, - ourMessageIndex: ourLogIndex, - ourHtlcIndex: ourHtlcIndex, - theirMessageIndex: theirLogIndex, - theirHtlcIndex: theirHtlcIndex, - height: nextHeight, - feePerKw: feePerKw, - dustLimit: dustLimit, - whoseCommit: whoseCommitChain, + ourBalance: commitTx.ourBalance, + theirBalance: commitTx.theirBalance, + txn: commitTx.txn, + fee: commitTx.fee, + messageIndices: messageIndices, + ourHtlcIndex: ourHtlcIndex, + theirHtlcIndex: theirHtlcIndex, + height: nextHeight, + feePerKw: feePerKw, + dustLimit: dustLimit, + whoseCommit: whoseCommitChain, + customBlob: newCommitBlob, } // In order to ensure _none_ of the HTLC's associated with this new // commitment are mutated, we'll manually copy over each HTLC to its // respective slice. - c.outgoingHTLCs = make([]PaymentDescriptor, len(filteredHTLCView.ourUpdates)) - for i, htlc := range filteredHTLCView.ourUpdates { + c.outgoingHTLCs = make( + []paymentDescriptor, len(filteredHTLCView.OurUpdates), + ) + for i, htlc := range filteredHTLCView.OurUpdates { c.outgoingHTLCs[i] = *htlc } - c.incomingHTLCs = make([]PaymentDescriptor, len(filteredHTLCView.theirUpdates)) - for i, htlc := range filteredHTLCView.theirUpdates { + c.incomingHTLCs = make( + []paymentDescriptor, len(filteredHTLCView.TheirUpdates), + ) + for i, htlc := range filteredHTLCView.TheirUpdates { c.incomingHTLCs[i] = *htlc } @@ -2633,16 +2869,17 @@ func fundingTxIn(chanState *channeldb.OpenChannel) wire.TxIn { // once for each height, and only in concert with signing a new commitment. // TODO(halseth): return htlcs to mutate instead of mutating inside // method. -func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, +func (lc *LightningChannel) evaluateHTLCView(view *HtlcView, ourBalance, theirBalance *lnwire.MilliSatoshi, nextHeight uint64, - whoseCommitChain lntypes.ChannelParty, mutateState bool, -) (*htlcView, error) { + whoseCommitChain lntypes.ChannelParty, mutateState bool) (*HtlcView, + error) { // We initialize the view's fee rate to the fee rate of the unfiltered // view. If any fee updates are found when evaluating the view, it will // be updated. - newView := &htlcView{ - feePerKw: view.feePerKw, + newView := &HtlcView{ + FeePerKw: view.FeePerKw, + NextHeight: nextHeight, } // We use two maps, one for the local log and one for the remote log to @@ -2655,7 +2892,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, // First we run through non-add entries in both logs, populating the // skip sets and mutating the current chain state (crediting balances, // etc) to reflect the settle/timeout entry encountered. - for _, entry := range view.ourUpdates { + for _, entry := range view.OurUpdates { switch entry.EntryType { // Skip adds for now. They will be processed below. case Add: @@ -2676,6 +2913,7 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, if mutateState && entry.EntryType == Settle && whoseCommitChain.IsLocal() && entry.removeCommitHeightLocal == 0 { + lc.channelState.TotalMSatReceived += entry.Amount } @@ -2687,10 +2925,13 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, } skipThem[addEntry.HtlcIndex] = struct{}{} - processRemoveEntry(entry, ourBalance, theirBalance, - nextHeight, whoseCommitChain, true, mutateState) + + processRemoveEntry( + entry, ourBalance, theirBalance, nextHeight, + whoseCommitChain, true, mutateState, + ) } - for _, entry := range view.theirUpdates { + for _, entry := range view.TheirUpdates { switch entry.EntryType { // Skip adds for now. They will be processed below. case Add: @@ -2724,32 +2965,41 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, } skipUs[addEntry.HtlcIndex] = struct{}{} - processRemoveEntry(entry, ourBalance, theirBalance, - nextHeight, whoseCommitChain, false, mutateState) + + processRemoveEntry( + entry, ourBalance, theirBalance, nextHeight, + whoseCommitChain, false, mutateState, + ) } // Next we take a second pass through all the log entries, skipping any // settled HTLCs, and debiting the chain state balance due to any newly // added HTLCs. - for _, entry := range view.ourUpdates { + for _, entry := range view.OurUpdates { isAdd := entry.EntryType == Add if _, ok := skipUs[entry.HtlcIndex]; !isAdd || ok { continue } - processAddEntry(entry, ourBalance, theirBalance, nextHeight, - whoseCommitChain, false, mutateState) - newView.ourUpdates = append(newView.ourUpdates, entry) + processAddEntry( + entry, ourBalance, theirBalance, nextHeight, + whoseCommitChain, false, mutateState, + ) + + newView.OurUpdates = append(newView.OurUpdates, entry) } - for _, entry := range view.theirUpdates { + for _, entry := range view.TheirUpdates { isAdd := entry.EntryType == Add if _, ok := skipThem[entry.HtlcIndex]; !isAdd || ok { continue } - processAddEntry(entry, ourBalance, theirBalance, nextHeight, - whoseCommitChain, true, mutateState) - newView.theirUpdates = append(newView.theirUpdates, entry) + processAddEntry( + entry, ourBalance, theirBalance, nextHeight, + whoseCommitChain, true, mutateState, + ) + + newView.TheirUpdates = append(newView.TheirUpdates, entry) } return newView, nil @@ -2757,9 +3007,9 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance, // fetchParent is a helper that looks up update log parent entries in the // appropriate log. -func (lc *LightningChannel) fetchParent(entry *PaymentDescriptor, +func (lc *LightningChannel) fetchParent(entry *paymentDescriptor, whoseCommitChain, whoseUpdateLog lntypes.ChannelParty, -) (*PaymentDescriptor, error) { +) (*paymentDescriptor, error) { var ( updateLog *updateLog @@ -2767,10 +3017,10 @@ func (lc *LightningChannel) fetchParent(entry *PaymentDescriptor, ) if whoseUpdateLog.IsRemote() { - updateLog = lc.remoteUpdateLog + updateLog = lc.updateLogs.Remote logName = "remote" } else { - updateLog = lc.localUpdateLog + updateLog = lc.updateLogs.Local logName = "local" } @@ -2812,7 +3062,7 @@ func (lc *LightningChannel) fetchParent(entry *PaymentDescriptor, // If the HTLC hasn't yet been committed in either chain, then the height it // was committed is updated. Keeping track of this inclusion height allows us to // later compact the log once the change is fully committed in both chains. -func processAddEntry(htlc *PaymentDescriptor, ourBalance, +func processAddEntry(htlc *paymentDescriptor, ourBalance, theirBalance *lnwire.MilliSatoshi, nextHeight uint64, whoseCommitChain lntypes.ChannelParty, isIncoming, mutateState bool) { @@ -2850,7 +3100,7 @@ func processAddEntry(htlc *PaymentDescriptor, ourBalance, // processRemoveEntry processes a log entry which settles or times out a // previously added HTLC. If the removal entry has already been processed, it // is skipped. -func processRemoveEntry(htlc *PaymentDescriptor, ourBalance, +func processRemoveEntry(htlc *paymentDescriptor, ourBalance, theirBalance *lnwire.MilliSatoshi, nextHeight uint64, whoseCommitChain lntypes.ChannelParty, isIncoming, mutateState bool) { @@ -2899,9 +3149,9 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance, // processFeeUpdate processes a log update that updates the current commitment // fee. -func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, - whoseCommitChain lntypes.ChannelParty, mutateState bool, view *htlcView, -) { +func processFeeUpdate(feeUpdate *paymentDescriptor, nextHeight uint64, + whoseCommitChain lntypes.ChannelParty, mutateState bool, + view *HtlcView) { // Fee updates are applied for all commitments after they are // sent/received, so we consider them being added and removed at the @@ -2922,7 +3172,7 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, // If the update wasn't already locked in, update the current fee rate // to reflect this update. - view.feePerKw = chainfee.SatPerKWeight(feeUpdate.Amount.ToSatoshis()) + view.FeePerKw = chainfee.SatPerKWeight(feeUpdate.Amount.ToSatoshis()) if mutateState { *addHeight = nextHeight @@ -2937,9 +3187,17 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, // signature can be submitted to the sigPool to generate all the signatures // asynchronously and in parallel. func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, - chanType channeldb.ChannelType, isRemoteInitiator bool, - leaseExpiry uint32, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - remoteCommitView *commitment) ([]SignJob, chan struct{}, error) { + chanState *channeldb.OpenChannel, leaseExpiry uint32, + remoteCommitView *commitment, + leafStore fn.Option[AuxLeafStore]) ([]SignJob, []AuxSigJob, + chan struct{}, error) { + + var ( + isRemoteInitiator = !chanState.IsInitiator + localChanCfg = chanState.LocalChanCfg + remoteChanCfg = chanState.RemoteChanCfg + chanType = chanState.ChanType + ) txHash := remoteCommitView.txn.TxHash() dustLimit := remoteChanCfg.DustLimit @@ -2949,13 +3207,28 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // With the keys generated, we'll make a slice with enough capacity to // hold potentially all the HTLCs. The actual slice may be a bit // smaller (than its total capacity) and some HTLCs may be dust. - numSigs := (len(remoteCommitView.incomingHTLCs) + - len(remoteCommitView.outgoingHTLCs)) + numSigs := len(remoteCommitView.incomingHTLCs) + + len(remoteCommitView.outgoingHTLCs) sigBatch := make([]SignJob, 0, numSigs) + auxSigBatch := make([]AuxSigJob, 0, numSigs) var err error cancelChan := make(chan struct{}) + diskCommit := remoteCommitView.toDiskCommit(lntypes.Remote) + auxResult, err := fn.MapOptionZ( + leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(chanState), *diskCommit, + *keyRing, lntypes.Remote, + ) + }, + ).Unpack() + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to fetch aux leaves: "+ + "%w", err) + } + // For each outgoing and incoming HTLC, if the HTLC isn't considered a // dust output after taking into account second-level HTLC fees, then a // sigJob will be generated and appended to the current batch. @@ -2982,6 +3255,13 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, htlcFee := HtlcTimeoutFee(chanType, feePerKw) outputAmt := htlc.Amount.ToSatoshis() - htlcFee + auxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.IncomingHtlcLeaves + return leaves[htlc.HtlcIndex].SecondLevelLeaf + }, + )(auxResult.AuxLeaves) + // With the fee calculate, we can properly create the HTLC // timeout transaction using the HTLC amount minus the fee. op := wire.OutPoint{ @@ -2992,9 +3272,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, chanType, isRemoteInitiator, op, outputAmt, htlc.Timeout, uint32(remoteChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, + auxLeaf, ) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Construct a full hash cache as we may be signing a segwit v1 @@ -3023,10 +3304,16 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // If this is a taproot channel, then we'll need to set the // method type to ensure we generate a valid signature. if chanType.IsTaproot() { - sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod //nolint:lll + //nolint:lll + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod } sigBatch = append(sigBatch, sigJob) + + auxSigBatch = append(auxSigBatch, NewAuxSigJob( + sigJob, *keyRing, true, newAuxHtlcDescriptor(&htlc), + remoteCommitView.customBlob, auxLeaf, cancelChan, + )) } for _, htlc := range remoteCommitView.outgoingHTLCs { if HtlcIsDust( @@ -3049,6 +3336,13 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, htlcFee := HtlcSuccessFee(chanType, feePerKw) outputAmt := htlc.Amount.ToSatoshis() - htlcFee + auxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.OutgoingHtlcLeaves + return leaves[htlc.HtlcIndex].SecondLevelLeaf + }, + )(auxResult.AuxLeaves) + // With the proper output amount calculated, we can now // generate the success transaction using the remote party's // CSV delay. @@ -3060,9 +3354,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, chanType, isRemoteInitiator, op, outputAmt, uint32(remoteChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey, + auxLeaf, ) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Construct a full hash cache as we may be signing a segwit v1 @@ -3091,13 +3386,19 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // If this is a taproot channel, then we'll need to set the // method type to ensure we generate a valid signature. if chanType.IsTaproot() { - sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod //nolint:lll + //nolint:lll + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod } sigBatch = append(sigBatch, sigJob) + + auxSigBatch = append(auxSigBatch, NewAuxSigJob( + sigJob, *keyRing, false, newAuxHtlcDescriptor(&htlc), + remoteCommitView.customBlob, auxLeaf, cancelChan, + )) } - return sigBatch, cancelChan, nil + return sigBatch, auxSigBatch, cancelChan, nil } // createCommitDiff will create a commit diff given a new pending commitment @@ -3105,15 +3406,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // validate this new state. This function is called right before sending the // new commitment to the remote party. The commit diff returned contains all // information necessary for retransmission. -func (lc *LightningChannel) createCommitDiff( - newCommit *commitment, commitSig lnwire.Sig, - htlcSigs []lnwire.Sig) (*channeldb.CommitDiff, error) { - - // First, we need to convert the funding outpoint into the ID that's - // used on the wire to identify this channel. We'll use this shortly - // when recording the exact CommitSig message that we'll be sending - // out. - chanID := lnwire.NewChanIDFromOutPoint(lc.channelState.FundingOutpoint) +func (lc *LightningChannel) createCommitDiff(newCommit *commitment, + commitSig lnwire.Sig, htlcSigs []lnwire.Sig, + auxSigs []fn.Option[tlv.Blob]) (*channeldb.CommitDiff, error) { var ( logUpdates []channeldb.LogUpdate @@ -3127,7 +3422,7 @@ func (lc *LightningChannel) createCommitDiff( // were only just committed within this pending state. This will be the // set of items we need to retransmit if we reconnect and find that // they didn't process this new state fully. - for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() { pd := e.Value // If this entry wasn't committed at the exact height of this @@ -3139,90 +3434,42 @@ func (lc *LightningChannel) createCommitDiff( continue } - // Knowing that this update is a part of this new commitment, - // we'll create a log update and not its index in the log so - // we can later restore it properly if a restart occurs. - logUpdate := channeldb.LogUpdate{ - LogIndex: pd.LogIndex, - } - - // We'll map the type of the PaymentDescriptor to one of the + // We'll map the type of the paymentDescriptor to one of the // four messages that it corresponds to. With this set of // messages obtained, we can simply read from disk and re-send // them in the case of a needed channel sync. switch pd.EntryType { case Add: - htlc := &lnwire.UpdateAddHTLC{ - ChanID: chanID, - ID: pd.HtlcIndex, - Amount: pd.Amount, - Expiry: pd.Timeout, - PaymentHash: pd.RHash, - BlindingPoint: pd.BlindingPoint, - } - copy(htlc.OnionBlob[:], pd.OnionBlob) - logUpdate.UpdateMsg = htlc - // Gather any references for circuits opened by this Add // HTLC. if pd.OpenCircuitKey != nil { - openCircuitKeys = append(openCircuitKeys, - *pd.OpenCircuitKey) + openCircuitKeys = append( + openCircuitKeys, *pd.OpenCircuitKey, + ) } - logUpdates = append(logUpdates, logUpdate) - - // Short circuit here since an add should not have any - // of the references gathered in the case of settles, - // fails or malformed fails. - continue - - case Settle: - logUpdate.UpdateMsg = &lnwire.UpdateFulfillHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - PaymentPreimage: pd.RPreimage, + case Settle, Fail, MalformedFail: + // Gather the fwd pkg references from any settle or fail + // packets, if they exist. + if pd.SourceRef != nil { + ackAddRefs = append(ackAddRefs, *pd.SourceRef) } - - case Fail: - logUpdate.UpdateMsg = &lnwire.UpdateFailHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - Reason: pd.FailReason, + if pd.DestRef != nil { + settleFailRefs = append( + settleFailRefs, *pd.DestRef, + ) } - - case MalformedFail: - logUpdate.UpdateMsg = &lnwire.UpdateFailMalformedHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - ShaOnionBlob: pd.ShaOnionBlob, - FailureCode: pd.FailCode, + if pd.ClosedCircuitKey != nil { + closedCircuitKeys = append( + closedCircuitKeys, *pd.ClosedCircuitKey, + ) } case FeeUpdate: - // The Amount field holds the feerate denominated in - // msat. Since feerates are only denominated in sat/kw, - // we can convert it without loss of precision. - logUpdate.UpdateMsg = &lnwire.UpdateFee{ - ChanID: chanID, - FeePerKw: uint32(pd.Amount.ToSatoshis()), - } + // Nothing special to do. } - // Gather the fwd pkg references from any settle or fail - // packets, if they exist. - if pd.SourceRef != nil { - ackAddRefs = append(ackAddRefs, *pd.SourceRef) - } - if pd.DestRef != nil { - settleFailRefs = append(settleFailRefs, *pd.DestRef) - } - if pd.ClosedCircuitKey != nil { - closedCircuitKeys = append(closedCircuitKeys, - *pd.ClosedCircuitKey) - } - - logUpdates = append(logUpdates, logUpdate) + logUpdates = append(logUpdates, pd.toLogUpdate()) } // With the set of log updates mapped into wire messages, we'll now @@ -3230,15 +3477,65 @@ func (lc *LightningChannel) createCommitDiff( // disk. diskCommit := newCommit.toDiskCommit(lntypes.Remote) - return &channeldb.CommitDiff{ - Commitment: *diskCommit, - CommitSig: &lnwire.CommitSig{ - ChanID: lnwire.NewChanIDFromOutPoint( - lc.channelState.FundingOutpoint, - ), - CommitSig: commitSig, - HtlcSigs: htlcSigs, + // We prepare the commit sig message to be sent to the remote party. + commitSigMsg := &lnwire.CommitSig{ + ChanID: lnwire.NewChanIDFromOutPoint( + lc.channelState.FundingOutpoint, + ), + CommitSig: commitSig, + HtlcSigs: htlcSigs, + } + + // Encode and check the size of the custom records now. + auxCustomRecords, err := fn.MapOptionZ( + lc.auxSigner, + func(s AuxSigner) fn.Result[lnwire.CustomRecords] { + blobOption, err := s.PackSigs(auxSigs).Unpack() + if err != nil { + return fn.Err[lnwire.CustomRecords](err) + } + + // We now serialize the commit sig message without the + // custom records to make sure we have space for them. + var buf bytes.Buffer + err = commitSigMsg.Encode(&buf, 0) + if err != nil { + return fn.Err[lnwire.CustomRecords](err) + } + + // The number of available bytes is the max message size + // minus the size of the message without the custom + // records. We also subtract 8 bytes for encoding + // overhead of the custom records (just some safety + // padding). + available := lnwire.MaxMsgBody - buf.Len() - 8 + + blob := blobOption.UnwrapOr(nil) + if len(blob) > available { + err = fmt.Errorf("aux sigs size %d exceeds "+ + "max allowed size of %d", len(blob), + available) + + return fn.Err[lnwire.CustomRecords](err) + } + + records, err := lnwire.ParseCustomRecords(blob) + if err != nil { + return fn.Err[lnwire.CustomRecords](err) + } + + return fn.Ok(records) }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("error packing aux sigs: %w", err) + } + + commitSigMsg.CustomRecords = auxCustomRecords + + return &channeldb.CommitDiff{ + Commitment: *diskCommit, + CommitSig: commitSigMsg, LogUpdates: logUpdates, OpenedCircuitKeys: openCircuitKeys, ClosedCircuitKeys: closedCircuitKeys, @@ -3250,22 +3547,20 @@ func (lc *LightningChannel) createCommitDiff( // getUnsignedAckedUpdates returns all remote log updates that we haven't // signed for yet ourselves. func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate { - // First, we need to convert the funding outpoint into the ID that's - // used on the wire to identify this channel. - chanID := lnwire.NewChanIDFromOutPoint(lc.channelState.FundingOutpoint) - // Fetch the last remote update that we have signed for. - lastRemoteCommitted := lc.remoteCommitChain.tail().theirMessageIndex + lastRemoteCommitted := + lc.commitChains.Remote.tail().messageIndices.Remote // Fetch the last remote update that we have acked. - lastLocalCommitted := lc.localCommitChain.tail().theirMessageIndex + lastLocalCommitted := + lc.commitChains.Local.tail().messageIndices.Remote // We'll now run through the remote update log to locate the items that // we haven't signed for yet. This will be the set of items we need to // restore if we reconnect in order to produce the signature that the // remote party expects. var logUpdates []channeldb.LogUpdate - for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() { pd := e.Value // Skip all remote updates that we have already included in our @@ -3282,59 +3577,9 @@ func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate { continue } - logUpdate := channeldb.LogUpdate{ - LogIndex: pd.LogIndex, - } - - // We'll map the type of the PaymentDescriptor to one of the - // four messages that it corresponds to. - switch pd.EntryType { - case Add: - htlc := &lnwire.UpdateAddHTLC{ - ChanID: chanID, - ID: pd.HtlcIndex, - Amount: pd.Amount, - Expiry: pd.Timeout, - PaymentHash: pd.RHash, - BlindingPoint: pd.BlindingPoint, - } - copy(htlc.OnionBlob[:], pd.OnionBlob) - logUpdate.UpdateMsg = htlc - - case Settle: - logUpdate.UpdateMsg = &lnwire.UpdateFulfillHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - PaymentPreimage: pd.RPreimage, - } - - case Fail: - logUpdate.UpdateMsg = &lnwire.UpdateFailHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - Reason: pd.FailReason, - } - - case MalformedFail: - logUpdate.UpdateMsg = &lnwire.UpdateFailMalformedHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - ShaOnionBlob: pd.ShaOnionBlob, - FailureCode: pd.FailCode, - } - - case FeeUpdate: - // The Amount field holds the feerate denominated in - // msat. Since feerates are only denominated in sat/kw, - // we can convert it without loss of precision. - logUpdate.UpdateMsg = &lnwire.UpdateFee{ - ChanID: chanID, - FeePerKw: uint32(pd.Amount.ToSatoshis()), - } - } - - logUpdates = append(logUpdates, logUpdate) + logUpdates = append(logUpdates, pd.toLogUpdate()) } + return logUpdates } @@ -3476,17 +3721,17 @@ func (lc *LightningChannel) applyCommitFee( // commitment transaction in terms of the ChannelConstraints that we and our // remote peer agreed upon during the funding workflow. The // predict[Our|Their]Add should parameters should be set to a valid -// PaymentDescriptor if we are validating in the state when adding a new HTLC, +// paymentDescriptor if we are validating in the state when adding a new HTLC, // or nil otherwise. func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, ourLogCounter uint64, whoseCommitChain lntypes.ChannelParty, - buffer BufferType, predictOurAdd, predictTheirAdd *PaymentDescriptor, + buffer BufferType, predictOurAdd, predictTheirAdd *paymentDescriptor, ) error { // First fetch the initial balance before applying any updates. - commitChain := lc.localCommitChain + commitChain := lc.commitChains.Local if whoseCommitChain.IsRemote() { - commitChain = lc.remoteCommitChain + commitChain = lc.commitChains.Remote } ourInitialBalance := commitChain.tip().ourBalance theirInitialBalance := commitChain.tip().theirBalance @@ -3498,10 +3743,10 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // appropriate update log, in order to validate the sanity of the // commitment resulting from _actually adding_ this HTLC to the state. if predictOurAdd != nil { - view.ourUpdates = append(view.ourUpdates, predictOurAdd) + view.OurUpdates = append(view.OurUpdates, predictOurAdd) } if predictTheirAdd != nil { - view.theirUpdates = append(view.theirUpdates, predictTheirAdd) + view.TheirUpdates = append(view.TheirUpdates, predictTheirAdd) } ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView( @@ -3512,7 +3757,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, return err } - feePerKw := filteredView.feePerKw + feePerKw := filteredView.FeePerKw // Ensure that the fee being applied is enough to be relayed across the // network in a reasonable time frame. @@ -3607,7 +3852,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // validateUpdates take a set of updates, and validates them against // the passed channel constraints. - validateUpdates := func(updates []*PaymentDescriptor, + validateUpdates := func(updates []*paymentDescriptor, constraints *channeldb.ChannelConfig) error { // We keep track of the number of HTLCs in flight for the @@ -3656,7 +3901,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // First check that the remote updates won't violate it's channel // constraints. err = validateUpdates( - filteredView.theirUpdates, &lc.channelState.RemoteChanCfg, + filteredView.TheirUpdates, &lc.channelState.RemoteChanCfg, ) if err != nil { return err @@ -3665,7 +3910,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // Secondly check that our updates won't violate our channel // constraints. err = validateUpdates( - filteredView.ourUpdates, &lc.channelState.LocalChanCfg, + filteredView.OurUpdates, &lc.channelState.LocalChanCfg, ) if err != nil { return err @@ -3689,6 +3934,10 @@ type CommitSigs struct { // PartialSig is the musig2 partial signature for taproot commitment // transactions. PartialSig lnwire.OptPartialSigWithNonceTLV + + // AuxSigBlob is the blob containing all the auxiliary signatures for + // this new commitment state. + AuxSigBlob tlv.Blob } // NewCommitState wraps the various signatures needed to properly @@ -3713,7 +3962,11 @@ type NewCommitState struct { // any). The HTLC signatures are sorted according to the BIP 69 order of the // HTLC's on the commitment transaction. Finally, the new set of pending HTLCs // for the remote party's commitment are also returned. -func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { +// +//nolint:funlen +func (lc *LightningChannel) SignNextCommitment( + ctx context.Context) (*NewCommitState, error) { + lc.Lock() defer lc.Unlock() @@ -3736,7 +3989,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // party, then we're unable to create new states. Each time we create a // new state, we consume a prior revocation point. commitPoint := lc.channelState.RemoteNextRevocation - unacked := lc.remoteCommitChain.hasUnackedCommitment() + unacked := lc.commitChains.Remote.hasUnackedCommitment() if unacked || commitPoint == nil { lc.log.Tracef("waiting for remote ack=%v, nil "+ "RemoteNextRevocation: %v", unacked, commitPoint == nil) @@ -3744,8 +3997,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { } // Determine the last update on the remote log that has been locked in. - remoteACKedIndex := lc.localCommitChain.tail().theirMessageIndex - remoteHtlcIndex := lc.localCommitChain.tail().theirHtlcIndex + remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote + remoteHtlcIndex := lc.commitChains.Local.tail().theirHtlcIndex // Before we extend this new commitment to the remote commitment chain, // ensure that we aren't violating any of the constraints the remote @@ -3755,7 +4008,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // point all updates will have to get locked-in so we enforce the // minimum requirement. err := lc.validateCommitmentSanity( - remoteACKedIndex, lc.localUpdateLog.logIndex, lntypes.Remote, + remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote, NoBuffer, nil, nil, ) if err != nil { @@ -3778,8 +4031,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // _all_ of our changes (pending or committed) but only the remote // node's changes up to the last change we've ACK'd. newCommitView, err := lc.fetchCommitmentView( - lntypes.Remote, lc.localUpdateLog.logIndex, - lc.localUpdateLog.htlcCounter, remoteACKedIndex, + lntypes.Remote, lc.updateLogs.Local.logIndex, + lc.updateLogs.Local.htlcCounter, remoteACKedIndex, remoteHtlcIndex, keyRing, ) if err != nil { @@ -3789,7 +4042,7 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { lc.log.Tracef("extending remote chain to height %v, "+ "local_log=%v, remote_log=%v", newCommitView.height, - lc.localUpdateLog.logIndex, remoteACKedIndex) + lc.updateLogs.Local.logIndex, remoteACKedIndex) lc.log.Tracef("remote chain: our_balance=%v, "+ "their_balance=%v, commit_tx: %v", @@ -3805,16 +4058,37 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { if lc.channelState.ChanType.HasLeaseExpiration() { leaseExpiry = lc.channelState.ThawHeight } - sigBatch, cancelChan, err := genRemoteHtlcSigJobs( - keyRing, lc.channelState.ChanType, !lc.channelState.IsInitiator, - leaseExpiry, &lc.channelState.LocalChanCfg, - &lc.channelState.RemoteChanCfg, newCommitView, + sigBatch, auxSigBatch, cancelChan, err := genRemoteHtlcSigJobs( + keyRing, lc.channelState, leaseExpiry, newCommitView, + lc.leafStore, ) if err != nil { return nil, err } + + // We'll need to send over the signatures to the remote party in the + // order as they appear on the commitment transaction after BIP 69 + // sorting. + slices.SortFunc(sigBatch, func(i, j SignJob) int { + return cmp.Compare(i.OutputIndex, j.OutputIndex) + }) + slices.SortFunc(auxSigBatch, func(i, j AuxSigJob) int { + return cmp.Compare(i.OutputIndex, j.OutputIndex) + }) + lc.sigPool.SubmitSignBatch(sigBatch) + err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error { + return a.SubmitSecondLevelSigBatch( + NewAuxChanState(lc.channelState), newCommitView.txn, + auxSigBatch, + ) + }) + if err != nil { + return nil, fmt.Errorf("error submitting second level sig "+ + "batch: %w", err) + } + // While the jobs are being carried out, we'll Sign their version of // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. @@ -3852,18 +4126,19 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { } } - // We'll need to send over the signatures to the remote party in the - // order as they appear on the commitment transaction after BIP 69 - // sorting. - sort.Slice(sigBatch, func(i, j int) bool { - return sigBatch[i].OutputIndex < sigBatch[j].OutputIndex - }) - - // With the jobs sorted, we'll now iterate through all the responses to - // gather each of the signatures in order. + // Iterate through all the responses to gather each of the signatures + // in the order they were submitted. htlcSigs = make([]lnwire.Sig, 0, len(sigBatch)) - for _, htlcSigJob := range sigBatch { - jobResp := <-htlcSigJob.Resp + auxSigs := make([]fn.Option[tlv.Blob], 0, len(auxSigBatch)) + for i := range sigBatch { + htlcSigJob := sigBatch[i] + var jobResp SignJobResp + + select { + case jobResp = <-htlcSigJob.Resp: + case <-ctx.Done(): + return nil, errQuit + } // If an error occurred, then we'll cancel any other active // jobs. @@ -3873,15 +4148,40 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { } htlcSigs = append(htlcSigs, jobResp.Sig) + + if lc.auxSigner.IsNone() { + continue + } + + auxHtlcSigJob := auxSigBatch[i] + var auxJobResp AuxSigJobResp + + select { + case auxJobResp = <-auxHtlcSigJob.Resp: + case <-ctx.Done(): + return nil, errQuit + } + + // If an error occurred, then we'll cancel any other active + // jobs. + if auxJobResp.Err != nil { + close(cancelChan) + return nil, auxJobResp.Err + } + + auxSigs = append(auxSigs, auxJobResp.SigBlob) } // As we're about to proposer a new commitment state for the remote // party, we'll write this pending state to disk before we exit, so we // can retransmit it if necessary. - commitDiff, err := lc.createCommitDiff(newCommitView, sig, htlcSigs) + commitDiff, err := lc.createCommitDiff( + newCommitView, sig, htlcSigs, auxSigs, + ) if err != nil { return nil, err } + err = lc.channelState.AppendRemoteCommitChain(commitDiff) if err != nil { return nil, err @@ -3893,13 +4193,20 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // Extend the remote commitment chain by one with the addition of our // latest commitment update. - lc.remoteCommitChain.addCommitment(newCommitView) + lc.commitChains.Remote.addCommitment(newCommitView) + + auxSigBlob, err := commitDiff.CommitSig.CustomRecords.Serialize() + if err != nil { + return nil, fmt.Errorf("unable to serialize aux sig blob: %w", + err) + } return &NewCommitState{ CommitSigs: &CommitSigs{ CommitSig: sig, HtlcSigs: htlcSigs, PartialSig: lnwire.MaybePartialSigWithNonce(partialSig), + AuxSigBlob: auxSigBlob, }, PendingHTLCs: commitDiff.Commitment.Htlcs, }, nil @@ -3911,8 +4218,8 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // each time. After we receive the channel reestablish message, we learn the // nonce we need to use for the remote party. As a result, we need to generate // the partial signature again with the new nonce. -func (lc *LightningChannel) resignMusigCommit(commitTx *wire.MsgTx, -) (lnwire.OptPartialSigWithNonceTLV, error) { +func (lc *LightningChannel) resignMusigCommit( + commitTx *wire.MsgTx) (lnwire.OptPartialSigWithNonceTLV, error) { remoteSession := lc.musigSessions.RemoteSession musig, err := remoteSession.SignCommit(commitTx) @@ -3947,7 +4254,9 @@ func (lc *LightningChannel) resignMusigCommit(commitTx *wire.MsgTx, // previous commitment txn. This allows the link to clear its mailbox of those // circuits in case they are still in memory, and ensure the switch's circuit // map has been updated by deleting the closed circuits. -func (lc *LightningChannel) ProcessChanSyncMsg( +// +//nolint:funlen +func (lc *LightningChannel) ProcessChanSyncMsg(ctx context.Context, msg *lnwire.ChannelReestablish) ([]lnwire.Message, []models.CircuitKey, []models.CircuitKey, error) { @@ -4030,9 +4339,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // Take note of our current commit chain heights before we begin adding // more to them. var ( - localTailHeight = lc.localCommitChain.tail().height - remoteTailHeight = lc.remoteCommitChain.tail().height - remoteTipHeight = lc.remoteCommitChain.tip().height + localTailHeight = lc.commitChains.Local.tail().height + remoteTailHeight = lc.commitChains.Remote.tail().height + remoteTipHeight = lc.commitChains.Remote.tip().height ) // We'll now check that their view of our local chain is up-to-date. @@ -4111,19 +4420,29 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // revocation, but also initiate a state transition to re-sync // them. if lc.OweCommitment() { - newCommit, err := lc.SignNextCommitment() + newCommit, err := lc.SignNextCommitment(ctx) switch { // If we signed this state, then we'll accumulate // another update to send over. case err == nil: + customRecords, err := lnwire.ParseCustomRecords( + newCommit.AuxSigBlob, + ) + if err != nil { + sErr := fmt.Errorf("error parsing aux "+ + "sigs: %w", err) + return nil, nil, nil, sErr + } + commitSig := &lnwire.CommitSig{ ChanID: lnwire.NewChanIDFromOutPoint( lc.channelState.FundingOutpoint, ), - CommitSig: newCommit.CommitSig, - HtlcSigs: newCommit.HtlcSigs, - PartialSig: newCommit.PartialSig, + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, + CustomRecords: customRecords, } updates = append(updates, commitSig) @@ -4200,7 +4519,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // Next, we'll need to send over any updates we sent as part of // this new proposed commitment state. for _, logUpdate := range commitDiff.LogUpdates { - commitUpdates = append(commitUpdates, logUpdate.UpdateMsg) + commitUpdates = append( + commitUpdates, logUpdate.UpdateMsg, + ) } // If this is a taproot channel, then we need to regenerate the @@ -4297,7 +4618,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg( return updates, openedCircuits, closedCircuits, nil } -// computeView takes the given htlcView, and calculates the balances, filtered +// computeView takes the given HtlcView, and calculates the balances, filtered // view (settling unsettled HTLCs), commitment weight and feePerKw, after // applying the HTLCs to the latest commitment. The returned balances are the // balances *before* subtracting the commitment fee from the initiator's @@ -4306,15 +4627,15 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // // If the updateState boolean is set true, the add and remove heights of the // HTLCs will be set to the next commitment height. -func (lc *LightningChannel) computeView(view *htlcView, +func (lc *LightningChannel) computeView(view *HtlcView, whoseCommitChain lntypes.ChannelParty, updateState bool, dryRunFee fn.Option[chainfee.SatPerKWeight]) (lnwire.MilliSatoshi, - lnwire.MilliSatoshi, lntypes.WeightUnit, *htlcView, error) { + lnwire.MilliSatoshi, lntypes.WeightUnit, *HtlcView, error) { - commitChain := lc.localCommitChain + commitChain := lc.commitChains.Local dustLimit := lc.channelState.LocalChanCfg.DustLimit if whoseCommitChain.IsRemote() { - commitChain = lc.remoteCommitChain + commitChain = lc.commitChains.Remote dustLimit = lc.channelState.RemoteChanCfg.DustLimit } @@ -4340,7 +4661,8 @@ func (lc *LightningChannel) computeView(view *htlcView, // Initiate feePerKw to the last committed fee for this chain as we'll // need this to determine which HTLCs are dust, and also the final fee // rate. - view.feePerKw = commitChain.tip().feePerKw + view.FeePerKw = commitChain.tip().feePerKw + view.NextHeight = nextHeight // We evaluate the view at this stage, meaning settled and failed HTLCs // will remove their corresponding added HTLCs. The resulting filtered @@ -4348,12 +4670,14 @@ func (lc *LightningChannel) computeView(view *htlcView, // channel constraints to the final commitment state. If any fee // updates are found in the logs, the commitment fee rate should be // changed, so we'll also set the feePerKw to this new value. - filteredHTLCView, err := lc.evaluateHTLCView(view, &ourBalance, - &theirBalance, nextHeight, whoseCommitChain, updateState) + filteredHTLCView, err := lc.evaluateHTLCView( + view, &ourBalance, &theirBalance, nextHeight, whoseCommitChain, + updateState, + ) if err != nil { return 0, 0, 0, nil, err } - feePerKw := filteredHTLCView.feePerKw + feePerKw := filteredHTLCView.FeePerKw // Here we override the view's fee-rate if a dry-run fee-rate was // passed in. @@ -4377,7 +4701,7 @@ func (lc *LightningChannel) computeView(view *htlcView, // Now go through all HTLCs at this stage, to calculate the total // weight, needed to calculate the transaction fee. var totalHtlcWeight lntypes.WeightUnit - for _, htlc := range filteredHTLCView.ourUpdates { + for _, htlc := range filteredHTLCView.OurUpdates { if HtlcIsDust( lc.channelState.ChanType, false, whoseCommitChain, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -4388,7 +4712,7 @@ func (lc *LightningChannel) computeView(view *htlcView, totalHtlcWeight += input.HTLCWeight } - for _, htlc := range filteredHTLCView.theirUpdates { + for _, htlc := range filteredHTLCView.TheirUpdates { if HtlcIsDust( lc.channelState.ChanType, true, whoseCommitChain, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -4409,10 +4733,19 @@ func (lc *LightningChannel) computeView(view *htlcView, // meant to verify all the signatures for HTLC's attached to a newly created // commitment state. The jobs generated are fully populated, and can be sent // directly into the pool of workers. -func genHtlcSigValidationJobs(localCommitmentView *commitment, - keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig, - chanType channeldb.ChannelType, isLocalInitiator bool, leaseExpiry uint32, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) { +// +//nolint:funlen +func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel, + localCommitmentView *commitment, keyRing *CommitmentKeyRing, + htlcSigs []lnwire.Sig, leaseExpiry uint32, + leafStore fn.Option[AuxLeafStore], auxSigner fn.Option[AuxSigner], + sigBlob fn.Option[tlv.Blob]) ([]VerifyJob, []AuxVerifyJob, error) { + + var ( + isLocalInitiator = chanState.IsInitiator + localChanCfg = chanState.LocalChanCfg + chanType = chanState.ChanType + ) txHash := localCommitmentView.txn.TxHash() feePerKw := localCommitmentView.feePerKw @@ -4422,9 +4755,36 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // enough capacity to hold verification jobs for all HTLC's in this // view. In the case that we have some dust outputs, then the actual // length will be smaller than the total capacity. - numHtlcs := (len(localCommitmentView.incomingHTLCs) + - len(localCommitmentView.outgoingHTLCs)) + numHtlcs := len(localCommitmentView.incomingHTLCs) + + len(localCommitmentView.outgoingHTLCs) verifyJobs := make([]VerifyJob, 0, numHtlcs) + auxVerifyJobs := make([]AuxVerifyJob, 0, numHtlcs) + + diskCommit := localCommitmentView.toDiskCommit(lntypes.Local) + auxResult, err := fn.MapOptionZ( + leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(chanState), *diskCommit, + *keyRing, lntypes.Local, + ) + }, + ).Unpack() + if err != nil { + return nil, nil, fmt.Errorf("unable to fetch aux leaves: %w", + err) + } + + // If we have a sig blob, then we'll attempt to map that to individual + // blobs for each HTLC we might need a signature for. + auxHtlcSigs, err := fn.MapOptionZ( + auxSigner, func(a AuxSigner) fn.Result[[]fn.Option[tlv.Blob]] { + return a.UnpackSigs(sigBlob) + }, + ).Unpack() + if err != nil { + return nil, nil, fmt.Errorf("error unpacking aux sigs: %w", + err) + } // We'll iterate through each output in the commitment transaction, // populating the sigHash closure function if it's detected to be an @@ -4436,6 +4796,9 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, htlcIndex uint64 sigHash func() ([]byte, error) sig input.Signature + htlc *paymentDescriptor + incoming bool + auxLeaf input.AuxTapLeaf err error ) @@ -4445,10 +4808,12 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // If this output index is found within the incoming HTLC // index, then this means that we need to generate an HTLC // success transaction in order to validate the signature. + //nolint:lll case localCommitmentView.incomingHTLCIndex[outputIndex] != nil: - htlc := localCommitmentView.incomingHTLCIndex[outputIndex] + htlc = localCommitmentView.incomingHTLCIndex[outputIndex] htlcIndex = htlc.HtlcIndex + incoming = true sigHash = func() ([]byte, error) { op := wire.OutPoint{ @@ -4459,11 +4824,19 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, htlcFee := HtlcSuccessFee(chanType, feePerKw) outputAmt := htlc.Amount.ToSatoshis() - htlcFee + auxLeaf := fn.ChainOption(func( + l CommitAuxLeaves) input.AuxTapLeaf { + + leaves := l.IncomingHtlcLeaves + idx := htlc.HtlcIndex + return leaves[idx].SecondLevelLeaf + })(auxResult.AuxLeaves) + successTx, err := CreateHtlcSuccessTx( chanType, isLocalInitiator, op, outputAmt, uint32(localChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, - keyRing.ToLocalKey, + keyRing.ToLocalKey, auxLeaf, ) if err != nil { return nil, err @@ -4506,7 +4879,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // Make sure there are more signatures left. if i >= len(htlcSigs) { - return nil, fmt.Errorf("not enough HTLC " + + return nil, nil, fmt.Errorf("not enough HTLC " + "signatures") } @@ -4522,15 +4895,16 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // is valid. sig, err = htlcSigs[i].ToSignature() if err != nil { - return nil, err + return nil, nil, err } htlc.sig = sig // Otherwise, if this is an outgoing HTLC, then we'll need to // generate a timeout transaction so we can verify the // signature presented. + //nolint:lll case localCommitmentView.outgoingHTLCIndex[outputIndex] != nil: - htlc := localCommitmentView.outgoingHTLCIndex[outputIndex] + htlc = localCommitmentView.outgoingHTLCIndex[outputIndex] htlcIndex = htlc.HtlcIndex @@ -4543,12 +4917,20 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, htlcFee := HtlcTimeoutFee(chanType, feePerKw) outputAmt := htlc.Amount.ToSatoshis() - htlcFee + auxLeaf := fn.ChainOption(func( + l CommitAuxLeaves) input.AuxTapLeaf { + + leaves := l.OutgoingHtlcLeaves + idx := htlc.HtlcIndex + return leaves[idx].SecondLevelLeaf + })(auxResult.AuxLeaves) + timeoutTx, err := CreateHtlcTimeoutTx( chanType, isLocalInitiator, op, outputAmt, htlc.Timeout, uint32(localChanCfg.CsvDelay), leaseExpiry, keyRing.RevocationKey, - keyRing.ToLocalKey, + keyRing.ToLocalKey, auxLeaf, ) if err != nil { return nil, err @@ -4593,7 +4975,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // Make sure there are more signatures left. if i >= len(htlcSigs) { - return nil, fmt.Errorf("not enough HTLC " + + return nil, nil, fmt.Errorf("not enough HTLC " + "signatures") } @@ -4609,7 +4991,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, // is valid. sig, err = htlcSigs[i].ToSignature() if err != nil { - return nil, err + return nil, nil, err } htlc.sig = sig @@ -4625,17 +5007,40 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, SigHash: sigHash, }) + if len(auxHtlcSigs) > i { + auxSig := auxHtlcSigs[i] + auxVerifyJob := NewAuxVerifyJob( + auxSig, *keyRing, incoming, + newAuxHtlcDescriptor(htlc), + localCommitmentView.customBlob, auxLeaf, + ) + + if htlc.CustomRecords == nil { + htlc.CustomRecords = make(lnwire.CustomRecords) + } + + // As this HTLC has a custom signature associated with + // it, store it in the custom records map so we can + // write to disk later. + sigType := htlcCustomSigType.TypeVal() + htlc.CustomRecords[uint64(sigType)] = auxSig.UnwrapOr( + nil, + ) + + auxVerifyJobs = append(auxVerifyJobs, auxVerifyJob) + } + i++ } // If we received a number of HTLC signatures that doesn't match our // commitment, we'll return an error now. if len(htlcSigs) != i { - return nil, fmt.Errorf("number of htlc sig mismatch. "+ + return nil, nil, fmt.Errorf("number of htlc sig mismatch. "+ "Expected %v sigs, got %v", i, len(htlcSigs)) } - return verifyJobs, nil + return verifyJobs, auxVerifyJobs, nil } // InvalidCommitSigError is a struct that implements the error interface to @@ -4737,8 +5142,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { } // Determine the last update on the local log that has been locked in. - localACKedIndex := lc.remoteCommitChain.tail().ourMessageIndex - localHtlcIndex := lc.remoteCommitChain.tail().ourHtlcIndex + localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local + localHtlcIndex := lc.commitChains.Remote.tail().ourHtlcIndex // Ensure that this new local update from the remote node respects all // the constraints we specified during initial channel setup. If not, @@ -4749,7 +5154,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { // the UpdateAddHTLC msg from our peer prior to receiving the // commit-sig). err := lc.validateCommitmentSanity( - lc.remoteUpdateLog.logIndex, localACKedIndex, lntypes.Local, + lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local, NoBuffer, nil, nil, ) if err != nil { @@ -4777,7 +5182,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { // up to the last change the remote node has ACK'd. localCommitmentView, err := lc.fetchCommitmentView( lntypes.Local, localACKedIndex, localHtlcIndex, - lc.remoteUpdateLog.logIndex, lc.remoteUpdateLog.htlcCounter, + lc.updateLogs.Remote.logIndex, lc.updateLogs.Remote.htlcCounter, keyRing, ) if err != nil { @@ -4787,13 +5192,18 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { lc.log.Tracef("extending local chain to height %v, "+ "local_log=%v, remote_log=%v", localCommitmentView.height, - localACKedIndex, lc.remoteUpdateLog.logIndex) + localACKedIndex, lc.updateLogs.Remote.logIndex) lc.log.Tracef("local chain: our_balance=%v, "+ "their_balance=%v, commit_tx: %v", localCommitmentView.ourBalance, localCommitmentView.theirBalance, lnutils.SpewLogClosure(localCommitmentView.txn)) + var auxSigBlob fn.Option[tlv.Blob] + if commitSigs.AuxSigBlob != nil { + auxSigBlob = fn.Some(commitSigs.AuxSigBlob) + } + // As an optimization, we'll generate a series of jobs for the worker // pool to verify each of the HTLC signatures presented. Once // generated, we'll submit these jobs to the worker pool. @@ -4801,11 +5211,10 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { if lc.channelState.ChanType.HasLeaseExpiration() { leaseExpiry = lc.channelState.ThawHeight } - verifyJobs, err := genHtlcSigValidationJobs( - localCommitmentView, keyRing, commitSigs.HtlcSigs, - lc.channelState.ChanType, lc.channelState.IsInitiator, - leaseExpiry, &lc.channelState.LocalChanCfg, - &lc.channelState.RemoteChanCfg, + verifyJobs, auxVerifyJobs, err := genHtlcSigValidationJobs( + lc.channelState, localCommitmentView, keyRing, + commitSigs.HtlcSigs, leaseExpiry, lc.leafStore, lc.auxSigner, + auxSigBlob, ) if err != nil { return err @@ -4943,7 +5352,11 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { } var txBytes bytes.Buffer - localCommitTx.Serialize(&txBytes) + err = localCommitTx.Serialize(&txBytes) + if err != nil { + return err + } + return &InvalidHtlcSigError{ commitHeight: nextHeight, htlcSig: sig.ToSignatureBytes(), @@ -4954,6 +5367,18 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { } } + // Now that we know all the normal sigs are valid, we'll also verify + // the aux jobs, if any exist. + err = fn.MapOptionZ(lc.auxSigner, func(a AuxSigner) error { + return a.VerifySecondLevelSigs( + NewAuxChanState(lc.channelState), localCommitTx, + auxVerifyJobs, + ) + }) + if err != nil { + return fmt.Errorf("unable to validate aux sigs: %w", err) + } + // The signature checks out, so we can now add the new commitment to // our local commitment chain. For regular channels, we can just // serialize the ECDSA sig. For taproot channels, we'll serialize the @@ -4977,7 +5402,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() //nolint:lll } - lc.localCommitChain.addCommitment(localCommitmentView) + lc.commitChains.Local.addCommitment(localCommitmentView) return nil } @@ -4995,13 +5420,13 @@ func (lc *LightningChannel) IsChannelClean() bool { defer lc.RUnlock() // Check whether we have a pending commitment for our local state. - if lc.localCommitChain.hasUnackedCommitment() { + if lc.commitChains.Local.hasUnackedCommitment() { return false } // Check whether our counterparty has a pending commitment for their // state. - if lc.remoteCommitChain.hasUnackedCommitment() { + if lc.commitChains.Remote.hasUnackedCommitment() { return false } @@ -5054,8 +5479,8 @@ func (lc *LightningChannel) oweCommitment(issuer lntypes.ChannelParty) bool { var ( remoteUpdatesPending, localUpdatesPending bool - lastLocalCommit = lc.localCommitChain.tip() - lastRemoteCommit = lc.remoteCommitChain.tip() + lastLocalCommit = lc.commitChains.Local.tip() + lastRemoteCommit = lc.commitChains.Remote.tip() perspective string ) @@ -5065,14 +5490,14 @@ func (lc *LightningChannel) oweCommitment(issuer lntypes.ChannelParty) bool { // There are local updates pending if our local update log is // not in sync with our remote commitment tx. - localUpdatesPending = lc.localUpdateLog.logIndex != - lastRemoteCommit.ourMessageIndex + localUpdatesPending = lc.updateLogs.Local.logIndex != + lastRemoteCommit.messageIndices.Local // There are remote updates pending if their remote commitment // tx (our local commitment tx) contains updates that we don't // have added to our remote commitment tx yet. - remoteUpdatesPending = lastLocalCommit.theirMessageIndex != - lastRemoteCommit.theirMessageIndex + remoteUpdatesPending = lastLocalCommit.messageIndices.Remote != + lastRemoteCommit.messageIndices.Remote } else { perspective = "remote" @@ -5080,14 +5505,14 @@ func (lc *LightningChannel) oweCommitment(issuer lntypes.ChannelParty) bool { // perspective of the remote party) if the remote party has // updates to their remote tx pending for which they haven't // signed yet. - localUpdatesPending = lc.remoteUpdateLog.logIndex != - lastLocalCommit.theirMessageIndex + localUpdatesPending = lc.updateLogs.Remote.logIndex != + lastLocalCommit.messageIndices.Remote // There are remote updates pending (remote updates from the // perspective of the remote party) if we have updates on our // remote commitment tx that they haven't added to theirs yet. - remoteUpdatesPending = lastRemoteCommit.ourMessageIndex != - lastLocalCommit.ourMessageIndex + remoteUpdatesPending = lastRemoteCommit.messageIndices.Local != + lastLocalCommit.messageIndices.Local } // If any of the conditions above is true, we owe a commitment @@ -5101,15 +5526,18 @@ func (lc *LightningChannel) oweCommitment(issuer lntypes.ChannelParty) bool { return oweCommitment } -// PendingLocalUpdateCount returns the number of local updates that still need -// to be applied to the remote commitment tx. -func (lc *LightningChannel) PendingLocalUpdateCount() uint64 { +// NumPendingUpdates returns the number of updates originated by whoseUpdates +// that have not been committed to the *tip* of whoseCommit's commitment chain. +func (lc *LightningChannel) NumPendingUpdates(whoseUpdates lntypes.ChannelParty, + whoseCommit lntypes.ChannelParty) uint64 { + lc.RLock() defer lc.RUnlock() - lastRemoteCommit := lc.remoteCommitChain.tip() + lastCommit := lc.commitChains.GetForParty(whoseCommit).tip() + updateIndex := lc.updateLogs.GetForParty(whoseUpdates).logIndex - return lc.localUpdateLog.logIndex - lastRemoteCommit.ourMessageIndex + return updateIndex - lastCommit.messageIndices.GetForParty(whoseUpdates) } // RevokeCurrentCommitment revokes the next lowest unrevoked commitment @@ -5132,16 +5560,16 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, } lc.log.Tracef("revoking height=%v, now at height=%v", - lc.localCommitChain.tail().height, + lc.commitChains.Local.tail().height, lc.currentHeight+1) // Advance our tail, as we've revoked our previous state. - lc.localCommitChain.advanceTail() + lc.commitChains.Local.advanceTail() lc.currentHeight++ // Additionally, generate a channel delta for this state transition for // persistent storage. - chainTail := lc.localCommitChain.tail() + chainTail := lc.commitChains.Local.tail() newCommitment := chainTail.toDiskCommit(lntypes.Local) // Get the unsigned acked remotes updates that are currently in memory. @@ -5179,15 +5607,10 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, // The returned values correspond to: // 1. The forwarding package corresponding to the remote commitment height // that was revoked. -// 2. The PaymentDescriptor of any Add HTLCs that were locked in by this -// revocation. -// 3. The PaymentDescriptor of any Settle/Fail HTLCs that were locked in by -// this revocation. -// 4. The set of HTLCs present on the current valid commitment transaction +// 2. The set of HTLCs present on the current valid commitment transaction // for the remote party. func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( - *channeldb.FwdPkg, []*PaymentDescriptor, []*PaymentDescriptor, - []channeldb.HTLC, error) { + *channeldb.FwdPkg, []channeldb.HTLC, error) { lc.Lock() defer lc.Unlock() @@ -5196,10 +5619,10 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( store := lc.channelState.RevocationStore revocation, err := chainhash.NewHash(revMsg.Revocation[:]) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } if err := store.AddNextEntry(revocation); err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } // Verify that if we use the commitment point computed based off of the @@ -5208,7 +5631,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( currentCommitPoint := lc.channelState.RemoteCurrentRevocation derivedCommitPoint := input.ComputeCommitmentPoint(revMsg.Revocation[:]) if !derivedCommitPoint.IsEqual(currentCommitPoint) { - return nil, nil, nil, nil, fmt.Errorf("revocation key mismatch") + return nil, nil, fmt.Errorf("revocation key mismatch") } // Now that we've verified that the prior commitment has been properly @@ -5221,16 +5644,15 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( lc.log.Tracef("remote party accepted state transition, revoked height "+ "%v, now at %v", - lc.remoteCommitChain.tail().height, - lc.remoteCommitChain.tail().height+1) + lc.commitChains.Remote.tail().height, + lc.commitChains.Remote.tail().height+1) // Add one to the remote tail since this will be height *after* we write // the revocation to disk, the local height will remain unchanged. - remoteChainTail := lc.remoteCommitChain.tail().height + 1 - localChainTail := lc.localCommitChain.tail().height + remoteChainTail := lc.commitChains.Remote.tail().height + 1 + localChainTail := lc.commitChains.Local.tail().height source := lc.ShortChanID() - chanID := lnwire.NewChanIDFromOutPoint(lc.channelState.FundingOutpoint) // Determine the set of htlcs that can be forwarded as a result of // having received the revocation. We will simultaneously construct the @@ -5238,14 +5660,12 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( // updates to disk and optimistically buffer the forwarding package in // memory. var ( - addsToForward []*PaymentDescriptor - addUpdates []channeldb.LogUpdate - settleFailsToForward []*PaymentDescriptor - settleFailUpdates []channeldb.LogUpdate + addUpdatesToForward []channeldb.LogUpdate + settleFailUpdatesToForward []channeldb.LogUpdate ) var addIndex, settleFailIndex uint16 - for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() { pd := e.Value // Fee updates are local to this particular channel, and should @@ -5293,7 +5713,13 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( addIndex++ pd.isForwarded = true - addsToForward = append(addsToForward, pd) + + // At this point we put the update into our list of + // updates that we will eventually put into the + // FwdPkg at this height. + addUpdatesToForward = append( + addUpdatesToForward, pd.toLogUpdate(), + ) case pd.EntryType != Add && committedRmv && shouldFwdRmv: // Construct a reference specifying the location that @@ -5307,79 +5733,36 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( settleFailIndex++ pd.isForwarded = true - settleFailsToForward = append(settleFailsToForward, pd) + + // At this point we put the update into our list of + // updates that we will eventually put into the + // FwdPkg at this height. + settleFailUpdatesToForward = append( + settleFailUpdatesToForward, pd.toLogUpdate(), + ) default: + // The update was not "freshly locked in" so we will + // ignore it as we construct the forwarding package. continue } - - // If we've reached this point, this HTLC will be added to the - // forwarding package at the height of the remote commitment. - // All types of HTLCs will record their assigned log index. - logUpdate := channeldb.LogUpdate{ - LogIndex: pd.LogIndex, - } - - // Next, we'll map the type of the PaymentDescriptor to one of - // the four messages that it corresponds to and separate the - // updates into Adds and Settle/Fail/MalformedFail such that - // they can be written in the forwarding package. Adds are - // aggregated separately from the other types of HTLCs. - switch pd.EntryType { - case Add: - htlc := &lnwire.UpdateAddHTLC{ - ChanID: chanID, - ID: pd.HtlcIndex, - Amount: pd.Amount, - Expiry: pd.Timeout, - PaymentHash: pd.RHash, - BlindingPoint: pd.BlindingPoint, - } - copy(htlc.OnionBlob[:], pd.OnionBlob) - logUpdate.UpdateMsg = htlc - addUpdates = append(addUpdates, logUpdate) - - case Settle: - logUpdate.UpdateMsg = &lnwire.UpdateFulfillHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - PaymentPreimage: pd.RPreimage, - } - settleFailUpdates = append(settleFailUpdates, logUpdate) - - case Fail: - logUpdate.UpdateMsg = &lnwire.UpdateFailHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - Reason: pd.FailReason, - } - settleFailUpdates = append(settleFailUpdates, logUpdate) - - case MalformedFail: - logUpdate.UpdateMsg = &lnwire.UpdateFailMalformedHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - ShaOnionBlob: pd.ShaOnionBlob, - FailureCode: pd.FailCode, - } - settleFailUpdates = append(settleFailUpdates, logUpdate) - } } // We use the remote commitment chain's tip as it will soon become the tail // once advanceTail is called. - remoteMessageIndex := lc.remoteCommitChain.tip().ourMessageIndex - localMessageIndex := lc.localCommitChain.tail().ourMessageIndex + remoteMessageIndex := lc.commitChains.Remote.tip().messageIndices.Local + localMessageIndex := lc.commitChains.Local.tail().messageIndices.Local localPeerUpdates := lc.unsignedLocalUpdates( - remoteMessageIndex, localMessageIndex, chanID, + remoteMessageIndex, localMessageIndex, ) // Now that we have gathered the set of HTLCs to forward, separated by // type, construct a forwarding package using the height that the remote // commitment chain will be extended after persisting the revocation. fwdPkg := channeldb.NewFwdPkg( - source, remoteChainTail, addUpdates, settleFailUpdates, + source, remoteChainTail, addUpdatesToForward, + settleFailUpdatesToForward, ) // We will soon be saving the current remote commitment to revocation @@ -5389,10 +5772,10 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( // before the change since the indexes are meant for the current, // revoked remote commitment. ourOutputIndex, theirOutputIndex, err := findOutputIndexesFromRemote( - revocation, lc.channelState, + revocation, lc.channelState, lc.leafStore, ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } // Now that we have a new verification nonce from them, we can refresh @@ -5400,7 +5783,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( if lc.channelState.ChanType.IsTaproot() { localNonce, err := revMsg.LocalNonce.UnwrapOrErrV(errNoNonce) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } session, err := lc.musigSessions.RemoteSession.Refresh( @@ -5409,7 +5792,7 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( }, ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } lc.musigSessions.RemoteSession = session @@ -5425,24 +5808,24 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( ourOutputIndex, theirOutputIndex, ) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, err } // Since they revoked the current lowest height in their commitment // chain, we can advance their chain by a single commitment. - lc.remoteCommitChain.advanceTail() + lc.commitChains.Remote.advanceTail() // As we've just completed a new state transition, attempt to see if we // can remove any entries from the update log which have been removed // from the PoV of both commitment chains. compactLogs( - lc.localUpdateLog, lc.remoteUpdateLog, localChainTail, + lc.updateLogs.Local, lc.updateLogs.Remote, localChainTail, remoteChainTail, ) remoteHTLCs := lc.channelState.RemoteCommitment.Htlcs - return fwdPkg, addsToForward, settleFailsToForward, remoteHTLCs, nil + return fwdPkg, remoteHTLCs, nil } // LoadFwdPkgs loads any pending log updates from disk and returns the payment @@ -5542,7 +5925,7 @@ func (lc *LightningChannel) addHTLC(htlc *lnwire.UpdateAddHTLC, return 0, err } - lc.localUpdateLog.appendHtlc(pd) + lc.updateLogs.Local.appendHtlc(pd) return pd.HtlcIndex, nil } @@ -5575,7 +5958,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty, feeRate = dryRunFee.UnwrapOr(feeRate) // Grab all of our HTLCs and evaluate against the dust limit. - for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() { pd := e.Value if pd.EntryType != Add { continue @@ -5594,7 +5977,7 @@ func (lc *LightningChannel) GetDustSum(whoseCommit lntypes.ChannelParty, } // Grab all of their HTLCs and evaluate against the dust limit. - for e := lc.remoteUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Remote.Front(); e != nil; e = e.Next() { pd := e.Value if pd.EntryType != Add { continue @@ -5667,33 +6050,35 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error { // htlcAddDescriptor returns a payment descriptor for the htlc and open key // provided to add to our local update log. func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC, - openKey *models.CircuitKey) *PaymentDescriptor { + openKey *models.CircuitKey) *paymentDescriptor { - return &PaymentDescriptor{ + return &paymentDescriptor{ + ChanID: htlc.ChanID, EntryType: Add, RHash: PaymentHash(htlc.PaymentHash), Timeout: htlc.Expiry, Amount: htlc.Amount, - LogIndex: lc.localUpdateLog.logIndex, - HtlcIndex: lc.localUpdateLog.htlcCounter, - OnionBlob: htlc.OnionBlob[:], + LogIndex: lc.updateLogs.Local.logIndex, + HtlcIndex: lc.updateLogs.Local.htlcCounter, + OnionBlob: htlc.OnionBlob, OpenCircuitKey: openKey, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } } // validateAddHtlc validates the addition of an outgoing htlc to our local and // remote commitments. -func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor, +func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor, buffer BufferType) error { // Make sure adding this HTLC won't violate any of the constraints we // must keep on the commitment transactions. - remoteACKedIndex := lc.localCommitChain.tail().theirMessageIndex + remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote // First we'll check whether this HTLC can be added to the remote // commitment transaction without violation any of the constraints. err := lc.validateCommitmentSanity( - remoteACKedIndex, lc.localUpdateLog.logIndex, lntypes.Remote, + remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote, buffer, pd, nil, ) if err != nil { @@ -5706,7 +6091,7 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor, // concurrently, but if we fail this check there is for sure not // possible for us to add the HTLC. err = lc.validateCommitmentSanity( - lc.remoteUpdateLog.logIndex, lc.localUpdateLog.logIndex, + lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex, lntypes.Local, buffer, pd, nil, ) if err != nil { @@ -5719,27 +6104,32 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor, // ReceiveHTLC adds an HTLC to the state machine's remote update log. This // method should be called in response to receiving a new HTLC from the remote // party. -func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error) { +func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, + error) { + lc.Lock() defer lc.Unlock() - if htlc.ID != lc.remoteUpdateLog.htlcCounter { - return 0, fmt.Errorf("ID %d on HTLC add does not match expected next "+ - "ID %d", htlc.ID, lc.remoteUpdateLog.htlcCounter) + if htlc.ID != lc.updateLogs.Remote.htlcCounter { + return 0, fmt.Errorf("ID %d on HTLC add does not match "+ + "expected next ID %d", htlc.ID, + lc.updateLogs.Remote.htlcCounter) } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: htlc.ChanID, EntryType: Add, RHash: PaymentHash(htlc.PaymentHash), Timeout: htlc.Expiry, Amount: htlc.Amount, - LogIndex: lc.remoteUpdateLog.logIndex, - HtlcIndex: lc.remoteUpdateLog.htlcCounter, - OnionBlob: htlc.OnionBlob[:], + LogIndex: lc.updateLogs.Remote.logIndex, + HtlcIndex: lc.updateLogs.Remote.htlcCounter, + OnionBlob: htlc.OnionBlob, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } - localACKedIndex := lc.remoteCommitChain.tail().ourMessageIndex + localACKedIndex := lc.commitChains.Remote.tail().messageIndices.Local // Clamp down on the number of HTLC's we can receive by checking the // commitment sanity. @@ -5748,14 +6138,14 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err // we use it here. The current lightning protocol does not allow to // reject ADDs already sent by the peer. err := lc.validateCommitmentSanity( - lc.remoteUpdateLog.logIndex, localACKedIndex, lntypes.Local, + lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local, NoBuffer, nil, pd, ) if err != nil { return 0, err } - lc.remoteUpdateLog.appendHtlc(pd) + lc.updateLogs.Remote.appendHtlc(pd) return pd.HtlcIndex, nil } @@ -5791,7 +6181,7 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte, lc.Lock() defer lc.Unlock() - htlc := lc.remoteUpdateLog.lookupHtlc(htlcIndex) + htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex) if htlc == nil { return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } @@ -5799,7 +6189,7 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte, // Now that we know the HTLC exists, before checking to see if the // preimage matches, we'll ensure that we haven't already attempted to // modify the HTLC. - if lc.remoteUpdateLog.htlcHasModification(htlcIndex) { + if lc.updateLogs.Remote.htlcHasModification(htlcIndex) { return ErrHtlcIndexAlreadySettled(htlcIndex) } @@ -5807,10 +6197,11 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte, return ErrInvalidSettlePreimage{preimage[:], htlc.RHash[:]} } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), Amount: htlc.Amount, RPreimage: preimage, - LogIndex: lc.localUpdateLog.logIndex, + LogIndex: lc.updateLogs.Local.logIndex, ParentIndex: htlcIndex, EntryType: Settle, SourceRef: sourceRef, @@ -5818,12 +6209,12 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte, ClosedCircuitKey: closeKey, } - lc.localUpdateLog.appendUpdate(pd) + lc.updateLogs.Local.appendUpdate(pd) // With the settle added to our local log, we'll now mark the HTLC as // modified to prevent ourselves from accidentally attempting a // duplicate settle. - lc.remoteUpdateLog.markHtlcModified(htlcIndex) + lc.updateLogs.Remote.markHtlcModified(htlcIndex) return nil } @@ -5836,7 +6227,7 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint6 lc.Lock() defer lc.Unlock() - htlc := lc.localUpdateLog.lookupHtlc(htlcIndex) + htlc := lc.updateLogs.Local.lookupHtlc(htlcIndex) if htlc == nil { return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } @@ -5844,7 +6235,7 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint6 // Now that we know the HTLC exists, before checking to see if the // preimage matches, we'll ensure that they haven't already attempted // to modify the HTLC. - if lc.localUpdateLog.htlcHasModification(htlcIndex) { + if lc.updateLogs.Local.htlcHasModification(htlcIndex) { return ErrHtlcIndexAlreadySettled(htlcIndex) } @@ -5852,21 +6243,22 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint6 return ErrInvalidSettlePreimage{preimage[:], htlc.RHash[:]} } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), Amount: htlc.Amount, RPreimage: preimage, ParentIndex: htlc.HtlcIndex, RHash: htlc.RHash, - LogIndex: lc.remoteUpdateLog.logIndex, + LogIndex: lc.updateLogs.Remote.logIndex, EntryType: Settle, } - lc.remoteUpdateLog.appendUpdate(pd) + lc.updateLogs.Remote.appendUpdate(pd) // With the settle added to the remote log, we'll now mark the HTLC as // modified to prevent the remote party from accidentally attempting a // duplicate settle. - lc.localUpdateLog.markHtlcModified(htlcIndex) + lc.updateLogs.Local.markHtlcModified(htlcIndex) return nil } @@ -5902,22 +6294,23 @@ func (lc *LightningChannel) FailHTLC(htlcIndex uint64, reason []byte, lc.Lock() defer lc.Unlock() - htlc := lc.remoteUpdateLog.lookupHtlc(htlcIndex) + htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex) if htlc == nil { return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } // Now that we know the HTLC exists, we'll ensure that we haven't // already attempted to fail the HTLC. - if lc.remoteUpdateLog.htlcHasModification(htlcIndex) { + if lc.updateLogs.Remote.htlcHasModification(htlcIndex) { return ErrHtlcIndexAlreadyFailed(htlcIndex) } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), Amount: htlc.Amount, RHash: htlc.RHash, ParentIndex: htlcIndex, - LogIndex: lc.localUpdateLog.logIndex, + LogIndex: lc.updateLogs.Local.logIndex, EntryType: Fail, FailReason: reason, SourceRef: sourceRef, @@ -5925,12 +6318,12 @@ func (lc *LightningChannel) FailHTLC(htlcIndex uint64, reason []byte, ClosedCircuitKey: closeKey, } - lc.localUpdateLog.appendUpdate(pd) + lc.updateLogs.Local.appendUpdate(pd) // With the fail added to the remote log, we'll now mark the HTLC as // modified to prevent ourselves from accidentally attempting a // duplicate fail. - lc.remoteUpdateLog.markHtlcModified(htlcIndex) + lc.updateLogs.Remote.markHtlcModified(htlcIndex) return nil } @@ -5952,34 +6345,35 @@ func (lc *LightningChannel) MalformedFailHTLC(htlcIndex uint64, lc.Lock() defer lc.Unlock() - htlc := lc.remoteUpdateLog.lookupHtlc(htlcIndex) + htlc := lc.updateLogs.Remote.lookupHtlc(htlcIndex) if htlc == nil { return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } // Now that we know the HTLC exists, we'll ensure that we haven't // already attempted to fail the HTLC. - if lc.remoteUpdateLog.htlcHasModification(htlcIndex) { + if lc.updateLogs.Remote.htlcHasModification(htlcIndex) { return ErrHtlcIndexAlreadyFailed(htlcIndex) } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), Amount: htlc.Amount, RHash: htlc.RHash, ParentIndex: htlcIndex, - LogIndex: lc.localUpdateLog.logIndex, + LogIndex: lc.updateLogs.Local.logIndex, EntryType: MalformedFail, FailCode: failCode, ShaOnionBlob: shaOnionBlob, SourceRef: sourceRef, } - lc.localUpdateLog.appendUpdate(pd) + lc.updateLogs.Local.appendUpdate(pd) // With the fail added to the remote log, we'll now mark the HTLC as // modified to prevent ourselves from accidentally attempting a // duplicate fail. - lc.remoteUpdateLog.markHtlcModified(htlcIndex) + lc.updateLogs.Remote.markHtlcModified(htlcIndex) return nil } @@ -5994,32 +6388,33 @@ func (lc *LightningChannel) ReceiveFailHTLC(htlcIndex uint64, reason []byte, lc.Lock() defer lc.Unlock() - htlc := lc.localUpdateLog.lookupHtlc(htlcIndex) + htlc := lc.updateLogs.Local.lookupHtlc(htlcIndex) if htlc == nil { return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } // Now that we know the HTLC exists, we'll ensure that they haven't // already attempted to fail the HTLC. - if lc.localUpdateLog.htlcHasModification(htlcIndex) { + if lc.updateLogs.Local.htlcHasModification(htlcIndex) { return ErrHtlcIndexAlreadyFailed(htlcIndex) } - pd := &PaymentDescriptor{ + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), Amount: htlc.Amount, RHash: htlc.RHash, ParentIndex: htlc.HtlcIndex, - LogIndex: lc.remoteUpdateLog.logIndex, + LogIndex: lc.updateLogs.Remote.logIndex, EntryType: Fail, FailReason: reason, } - lc.remoteUpdateLog.appendUpdate(pd) + lc.updateLogs.Remote.appendUpdate(pd) // With the fail added to the remote log, we'll now mark the HTLC as // modified to prevent ourselves from accidentally attempting a // duplicate fail. - lc.localUpdateLog.markHtlcModified(htlcIndex) + lc.updateLogs.Local.markHtlcModified(htlcIndex) return nil } @@ -6031,6 +6426,12 @@ func (lc *LightningChannel) ChannelPoint() wire.OutPoint { return lc.channelState.FundingOutpoint } +// ChannelID returns the ChannelID of this LightningChannel. This is the same +// ChannelID that is used in update messages for this channel. +func (lc *LightningChannel) ChannelID() lnwire.ChannelID { + return lnwire.NewChanIDFromOutPoint(lc.ChannelPoint()) +} + // ShortChanID returns the short channel ID for the channel. The short channel // ID encodes the exact location in the main chain that the original // funding output can be found. @@ -6088,11 +6489,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { "verification nonce: %w", err) } + tapscriptTweak := fn.MapOption(TapscriptRootToTweak)( + lc.channelState.TapscriptRoot, + ) + // Now that we have the local nonce, we'll re-create the musig // session we had for this height. musigSession := NewPartialMusigSession( *localNonce, ourKey, theirKey, lc.Signer, - &lc.fundingOutput, LocalMusigCommit, + &lc.fundingOutput, LocalMusigCommit, tapscriptTweak, ) var remoteSig lnwire.PartialSigWithNonce @@ -6186,6 +6591,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 @@ -6240,10 +6650,12 @@ type UnilateralCloseSummary struct { // happen in case we have lost state) it should be set to an empty struct, in // which case we will attempt to sweep the non-HTLC output using the passed // commitPoint. -func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Signer, - commitSpend *chainntnfs.SpendDetail, - remoteCommit channeldb.ChannelCommitment, - commitPoint *btcec.PublicKey) (*UnilateralCloseSummary, error) { +func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen + signer input.Signer, commitSpend *chainntnfs.SpendDetail, + remoteCommit channeldb.ChannelCommitment, commitPoint *btcec.PublicKey, + 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. @@ -6253,43 +6665,59 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) + auxResult, err := fn.MapOptionZ( + leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(chanState), remoteCommit, + *keyRing, lntypes.Remote, + ) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) + } + // 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, &chanState.RemoteChanCfg, commitSpend.SpendingTx, - chanState.ChanType, isRemoteInitiator, leaseExpiry, + chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState, + auxResult.AuxLeaves, auxResolver, ) 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. + remoteAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.RemoteAuxLeaf + }, + )(auxResult.AuxLeaves) selfScript, maturityDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, remoteAuxLeaf, ) 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{ @@ -6352,6 +6780,36 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si 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, + ChanType: chanState.ChanType, + 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{ @@ -6445,6 +6903,11 @@ type IncomingHtlcResolution struct { // necessary items required to spend the sole output of the above // transaction. SweepSignDesc input.SignDescriptor + + // 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] } // OutgoingHtlcResolution houses the information necessary to sweep any @@ -6494,6 +6957,11 @@ type OutgoingHtlcResolution struct { // necessary items required to spend the sole output of the above // transaction. SweepSignDesc input.SignDescriptor + + // 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] } // HtlcResolutions contains the items necessary to sweep HTLC's on chain @@ -6518,7 +6986,10 @@ func newOutgoingHtlcResolution(signer input.Signer, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, - chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) { + chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, + auxLeaves fn.Option[CommitAuxLeaves], + auxResolver fn.Option[AuxContractResolver], +) (*OutgoingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitTx.TxHash(), @@ -6527,9 +6998,12 @@ func newOutgoingHtlcResolution(signer input.Signer, // First, we'll re-generate the script used to send the HTLC to the // remote party within their commitment transaction. + auxLeaf := fn.ChainOption(func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.OutgoingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf + })(auxLeaves) htlcScriptInfo, err := genHtlcScript( chanType, false, whoseCommit, htlc.RefundTimeout, htlc.RHash, - keyRing, + keyRing, auxLeaf, ) if err != nil { return nil, err @@ -6546,6 +7020,8 @@ func newOutgoingHtlcResolution(signer input.Signer, return nil, err } + htlcCsvDelay := HtlcSecondLevelInputSequence(chanType) + // If we're spending this HTLC output from the remote node's // commitment, then we won't need to go to the second level as our // outputs don't have a CSV delay. @@ -6583,11 +7059,43 @@ func newOutgoingHtlcResolution(signer input.Signer, } } + resReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ChanType: chanType, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.RemoteCommitment.CustomBlob, + FundingBlob: chanState.CustomBlob, + Type: input.TaprootHtlcOfferedRemoteTimeout, + CloseType: RemoteForceClose, + CommitTx: commitTx, + ContractPoint: op, + SignDesc: signDesc, + KeyRing: keyRing, + CsvDelay: htlcCsvDelay, + CltvDelay: fn.Some(htlc.RefundTimeout), + CommitFee: chanState.RemoteCommitment.CommitFee, + HtlcID: fn.Some(htlc.HtlcIndex), + PayHash: fn.Some(htlc.RHash), + } + resolveRes := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + return a.ResolveContract(resReq) + }, + ) + if err := resolveRes.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + resolutionBlob := resolveRes.Option() + return &OutgoingHtlcResolution{ - Expiry: htlc.RefundTimeout, - ClaimOutpoint: op, - SweepSignDesc: signDesc, - CsvDelay: HtlcSecondLevelInputSequence(chanType), + Expiry: htlc.RefundTimeout, + ClaimOutpoint: op, + SweepSignDesc: signDesc, + CsvDelay: csvDelay, + ResolutionBlob: resolutionBlob, }, nil } @@ -6602,10 +7110,16 @@ func newOutgoingHtlcResolution(signer input.Signer, // With the fee calculated, re-construct the second level timeout // transaction. + secondLevelAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.OutgoingHtlcLeaves + return leaves[htlc.HtlcIndex].SecondLevelLeaf + }, + )(auxLeaves) timeoutTx, err := CreateHtlcTimeoutTx( chanType, isCommitFromInitiator, op, secondLevelOutputAmt, - htlc.RefundTimeout, csvDelay, leaseExpiry, keyRing.RevocationKey, - keyRing.ToLocalKey, + htlc.RefundTimeout, csvDelay, leaseExpiry, + keyRing.RevocationKey, keyRing.ToLocalKey, secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6688,6 +7202,7 @@ func newOutgoingHtlcResolution(signer input.Signer, htlcSweepScript, err = SecondLevelHtlcScript( chanType, isCommitFromInitiator, keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, leaseExpiry, + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6696,6 +7211,7 @@ func newOutgoingHtlcResolution(signer input.Signer, //nolint:lll secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6730,31 +7246,78 @@ func newOutgoingHtlcResolution(signer input.Signer, keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, ) + // In addition to the info in txSignDetails, we also need extra + // information to sweep the second level output after confirmation. + sweepSignDesc := input.SignDescriptor{ + KeyDesc: localChanCfg.DelayBasePoint, + SingleTweak: localDelayTweak, + WitnessScript: htlcSweepWitnessScript, + Output: &wire.TxOut{ + PkScript: htlcSweepScript.PkScript(), + Value: int64(secondLevelOutputAmt), + }, + HashType: sweepSigHash(chanType), + PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( + htlcSweepScript.PkScript(), + int64(secondLevelOutputAmt), + ), + SignMethod: signMethod, + ControlBlock: ctrlBlock, + } + + // This might be an aux channel, so we'll go ahead and attempt to + // generate the resolution blob for the channel so we can pass along to + // the sweeping sub-system. + resolveRes := fn.MapOptionZ( + auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] { + resReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ChanType: chanType, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll + FundingBlob: chanState.CustomBlob, + Type: input.TaprootHtlcLocalOfferedTimeout, //nolint:lll + CloseType: LocalForceClose, + CommitTx: commitTx, + ContractPoint: op, + SignDesc: sweepSignDesc, + KeyRing: keyRing, + CsvDelay: htlcCsvDelay, + HtlcAmt: btcutil.Amount(txOut.Value), + CommitCsvDelay: csvDelay, + CltvDelay: fn.Some(htlc.RefundTimeout), + CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll + HtlcID: fn.Some(htlc.HtlcIndex), + PayHash: fn.Some(htlc.RHash), + AuxSigDesc: fn.Some(AuxSigDesc{ + SignDetails: *txSignDetails, + AuxSig: func() []byte { + tlvType := htlcCustomSigType.TypeVal() //nolint:lll + return htlc.CustomRecords[uint64(tlvType)] //nolint:lll + }(), + }), + } + + return a.ResolveContract(resReq) + }, + ) + if err := resolveRes.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + resolutionBlob := resolveRes.Option() + return &OutgoingHtlcResolution{ Expiry: htlc.RefundTimeout, SignedTimeoutTx: timeoutTx, SignDetails: txSignDetails, CsvDelay: csvDelay, + ResolutionBlob: resolutionBlob, ClaimOutpoint: wire.OutPoint{ Hash: timeoutTx.TxHash(), Index: 0, }, - SweepSignDesc: input.SignDescriptor{ - KeyDesc: localChanCfg.DelayBasePoint, - SingleTweak: localDelayTweak, - WitnessScript: htlcSweepWitnessScript, - Output: &wire.TxOut{ - PkScript: htlcSweepScript.PkScript(), - Value: int64(secondLevelOutputAmt), - }, - HashType: sweepSigHash(chanType), - PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( - htlcSweepScript.PkScript(), - int64(secondLevelOutputAmt), - ), - SignMethod: signMethod, - ControlBlock: ctrlBlock, - }, + SweepSignDesc: sweepSignDesc, }, nil } @@ -6770,8 +7333,10 @@ func newIncomingHtlcResolution(signer input.Signer, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, - chanType channeldb.ChannelType) ( - *IncomingHtlcResolution, error) { + chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, + auxLeaves fn.Option[CommitAuxLeaves], + auxResolver fn.Option[AuxContractResolver], +) (*IncomingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitTx.TxHash(), @@ -6780,9 +7345,12 @@ func newIncomingHtlcResolution(signer input.Signer, // First, we'll re-generate the script the remote party used to // send the HTLC to us in their commitment transaction. + auxLeaf := fn.ChainOption(func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.IncomingHtlcLeaves[htlc.HtlcIndex].AuxTapLeaf + })(auxLeaves) scriptInfo, err := genHtlcScript( chanType, true, whoseCommit, htlc.RefundTimeout, htlc.RHash, - keyRing, + keyRing, auxLeaf, ) if err != nil { return nil, err @@ -6800,6 +7368,8 @@ func newIncomingHtlcResolution(signer input.Signer, return nil, err } + htlcCsvDelay := HtlcSecondLevelInputSequence(chanType) + // If we're spending this output from the remote node's commitment, // then we can skip the second layer and spend the output directly. if whoseCommit.IsRemote() { @@ -6835,13 +7405,54 @@ func newIncomingHtlcResolution(signer input.Signer, } } + resReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ChanType: chanType, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.RemoteCommitment.CustomBlob, + Type: input.TaprootHtlcAcceptedRemoteSuccess, + FundingBlob: chanState.CustomBlob, + CloseType: RemoteForceClose, + CommitTx: commitTx, + ContractPoint: op, + SignDesc: signDesc, + KeyRing: keyRing, + HtlcID: fn.Some(htlc.HtlcIndex), + CsvDelay: htlcCsvDelay, + CltvDelay: fn.Some(htlc.RefundTimeout), + CommitFee: chanState.RemoteCommitment.CommitFee, + PayHash: fn.Some(htlc.RHash), + CommitCsvDelay: csvDelay, + HtlcAmt: htlc.Amt.ToSatoshis(), + } + resolveRes := fn.MapOptionZ( + auxResolver, + func(a AuxContractResolver) fn.Result[tlv.Blob] { + return a.ResolveContract(resReq) + }, + ) + if err := resolveRes.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + resolutionBlob := resolveRes.Option() + return &IncomingHtlcResolution{ - ClaimOutpoint: op, - SweepSignDesc: signDesc, - CsvDelay: HtlcSecondLevelInputSequence(chanType), + ClaimOutpoint: op, + SweepSignDesc: signDesc, + CsvDelay: htlcCsvDelay, + ResolutionBlob: resolutionBlob, }, nil } + secondLevelAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + leaves := l.IncomingHtlcLeaves + return leaves[htlc.HtlcIndex].SecondLevelLeaf + }, + )(auxLeaves) + // Otherwise, we'll need to go to the second level to sweep this HTLC. // // First, we'll reconstruct the original HTLC success transaction, @@ -6851,7 +7462,7 @@ func newIncomingHtlcResolution(signer input.Signer, successTx, err := CreateHtlcSuccessTx( chanType, isCommitFromInitiator, op, secondLevelOutputAmt, csvDelay, leaseExpiry, keyRing.RevocationKey, - keyRing.ToLocalKey, + keyRing.ToLocalKey, secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6934,6 +7545,7 @@ func newIncomingHtlcResolution(signer input.Signer, htlcSweepScript, err = SecondLevelHtlcScript( chanType, isCommitFromInitiator, keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, leaseExpiry, + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6942,6 +7554,7 @@ func newIncomingHtlcResolution(signer input.Signer, //nolint:lll secondLevelScriptTree, err := input.TaprootSecondLevelScriptTree( keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, + secondLevelAuxLeaf, ) if err != nil { return nil, err @@ -6975,30 +7588,76 @@ func newIncomingHtlcResolution(signer input.Signer, localDelayTweak := input.SingleTweakBytes( keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey, ) + + // In addition to the info in txSignDetails, we also need extra + // information to sweep the second level output after confirmation. + sweepSignDesc := input.SignDescriptor{ + KeyDesc: localChanCfg.DelayBasePoint, + SingleTweak: localDelayTweak, + WitnessScript: htlcSweepWitnessScript, + Output: &wire.TxOut{ + PkScript: htlcSweepScript.PkScript(), + Value: int64(secondLevelOutputAmt), + }, + HashType: sweepSigHash(chanType), + PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( + htlcSweepScript.PkScript(), + int64(secondLevelOutputAmt), + ), + SignMethod: signMethod, + ControlBlock: ctrlBlock, + } + + resolveRes := fn.MapOptionZ( + auxResolver, func(a AuxContractResolver) fn.Result[tlv.Blob] { + resReq := ResolutionReq{ + ChanPoint: chanState.FundingOutpoint, + ChanType: chanType, + ShortChanID: chanState.ShortChanID(), + Initiator: chanState.IsInitiator, + CommitBlob: chanState.LocalCommitment.CustomBlob, //nolint:lll + Type: input.TaprootHtlcAcceptedLocalSuccess, //nolint:lll + FundingBlob: chanState.CustomBlob, + CloseType: LocalForceClose, + CommitTx: commitTx, + ContractPoint: op, + SignDesc: sweepSignDesc, + KeyRing: keyRing, + HtlcID: fn.Some(htlc.HtlcIndex), + CsvDelay: htlcCsvDelay, + CommitFee: chanState.LocalCommitment.CommitFee, //nolint:lll + PayHash: fn.Some(htlc.RHash), + AuxSigDesc: fn.Some(AuxSigDesc{ + SignDetails: *txSignDetails, + AuxSig: func() []byte { + tlvType := htlcCustomSigType.TypeVal() //nolint:lll + return htlc.CustomRecords[uint64(tlvType)] //nolint:lll + }(), + }), + CommitCsvDelay: csvDelay, + HtlcAmt: btcutil.Amount(txOut.Value), + CltvDelay: fn.Some(htlc.RefundTimeout), + } + + return a.ResolveContract(resReq) + }, + ) + if err := resolveRes.Err(); err != nil { + return nil, fmt.Errorf("unable to aux resolve: %w", err) + } + + resolutionBlob := resolveRes.Option() + return &IncomingHtlcResolution{ SignedSuccessTx: successTx, SignDetails: txSignDetails, CsvDelay: csvDelay, + ResolutionBlob: resolutionBlob, ClaimOutpoint: wire.OutPoint{ Hash: successTx.TxHash(), Index: 0, }, - SweepSignDesc: input.SignDescriptor{ - KeyDesc: localChanCfg.DelayBasePoint, - SingleTweak: localDelayTweak, - WitnessScript: htlcSweepWitnessScript, - Output: &wire.TxOut{ - PkScript: htlcSweepScript.PkScript(), - Value: int64(secondLevelOutputAmt), - }, - HashType: sweepSigHash(chanType), - PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( - htlcSweepScript.PkScript(), - int64(secondLevelOutputAmt), - ), - SignMethod: signMethod, - ControlBlock: ctrlBlock, - }, + SweepSignDesc: sweepSignDesc, }, nil } @@ -7034,7 +7693,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, chanType channeldb.ChannelType, - isCommitFromInitiator bool, leaseExpiry uint32) (*HtlcResolutions, error) { + isCommitFromInitiator bool, leaseExpiry uint32, + chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves], + auxResolver fn.Option[AuxContractResolver]) (*HtlcResolutions, error) { // TODO(roasbeef): don't need to swap csv delay? dustLimit := remoteChanCfg.DustLimit @@ -7067,8 +7728,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, // as we can satisfy the contract. ihr, err := newIncomingHtlcResolution( signer, localChanCfg, commitTx, &htlc, - keyRing, feePerKw, uint32(csvDelay), leaseExpiry, - whoseCommit, isCommitFromInitiator, chanType, + keyRing, feePerKw, uint32(csvDelay), + leaseExpiry, whoseCommit, isCommitFromInitiator, + chanType, chanState, auxLeaves, auxResolver, ) if err != nil { return nil, fmt.Errorf("incoming resolution "+ @@ -7082,7 +7744,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ohr, err := newOutgoingHtlcResolution( signer, localChanCfg, commitTx, &htlc, keyRing, feePerKw, uint32(csvDelay), leaseExpiry, whoseCommit, - isCommitFromInitiator, chanType, + isCommitFromInitiator, chanType, chanState, auxLeaves, + auxResolver, ) if err != nil { return nil, fmt.Errorf("outgoing resolution "+ @@ -7130,6 +7793,18 @@ type LocalForceCloseSummary struct { // commitment state. CloseTx *wire.MsgTx + // ChanSnapshot is a snapshot of the final state of the channel at the + // time the summary was created. + ChanSnapshot channeldb.ChannelSnapshot + + // ContractResolutions contains all the data required for resolving the + // different output types of a commitment transaction. + ContractResolutions fn.Option[ContractResolutions] +} + +// ContractResolutions contains all the data required for resolving the +// different output types of a commitment transaction. +type ContractResolutions struct { // CommitResolution contains all the data required to sweep the output // to ourselves. Since this is our commitment transaction, we'll need // to wait a time delay before we can sweep the output. @@ -7138,19 +7813,38 @@ type LocalForceCloseSummary struct { // then this will be nil. CommitResolution *CommitOutputResolution + // AnchorResolution contains the data required to sweep the anchor + // output. If the channel type doesn't include anchors, the value of + // this field will be nil. + AnchorResolution *AnchorResolution + // HtlcResolutions contains all the data required to sweep any outgoing // HTLC's and incoming HTLc's we know the preimage to. For each of these // HTLC's, we'll need to go to the second level to sweep them fully. HtlcResolutions *HtlcResolutions +} - // ChanSnapshot is a snapshot of the final state of the channel at the - // time the summary was created. - ChanSnapshot channeldb.ChannelSnapshot +// ForceCloseOpt is a functional option argument for the ForceClose method. +type ForceCloseOpt func(*forceCloseConfig) - // AnchorResolution contains the data required to sweep the anchor - // output. If the channel type doesn't include anchors, the value of - // this field will be nil. - AnchorResolution *AnchorResolution +// forceCloseConfig holds the configuration options for force closing a channel. +type forceCloseConfig struct { + // skipResolution if true will skip creating the contract resolutions + // when generating the force close summary. + skipResolution bool +} + +// defaultForceCloseConfig returns the default force close configuration. +func defaultForceCloseConfig() *forceCloseConfig { + return &forceCloseConfig{} +} + +// WithSkipContractResolutions creates an option to skip the contract +// resolutions from the returned summary. +func WithSkipContractResolutions() ForceCloseOpt { + return func(cfg *forceCloseConfig) { + cfg.skipResolution = true + } } // ForceClose executes a unilateral closure of the transaction at the current @@ -7161,10 +7855,17 @@ type LocalForceCloseSummary struct { // outputs within the commitment transaction. // // TODO(roasbeef): all methods need to abort if in dispute state -func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { +func (lc *LightningChannel) ForceClose(opts ...ForceCloseOpt) ( + *LocalForceCloseSummary, error) { + lc.Lock() defer lc.Unlock() + cfg := defaultForceCloseConfig() + for _, opt := range opts { + opt(cfg) + } + // If we've detected local data loss for this channel, then we won't // allow a force close, as it may be the case that we have a dated // version of the commitment, or this is actually a channel shell. @@ -7179,10 +7880,18 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { return nil, err } + if cfg.skipResolution { + return &LocalForceCloseSummary{ + ChanPoint: lc.channelState.FundingOutpoint, + ChanSnapshot: *lc.channelState.Snapshot(), + CloseTx: commitTx, + }, nil + } + localCommitment := lc.channelState.LocalCommitment summary, err := NewLocalForceCloseSummary( lc.channelState, lc.Signer, commitTx, - localCommitment.CommitHeight, + localCommitment.CommitHeight, lc.leafStore, lc.auxResolver, ) if err != nil { return nil, fmt.Errorf("unable to gen force close "+ @@ -7199,8 +7908,10 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { // channel state. The passed commitTx must be a fully signed commitment // transaction corresponding to localCommit. func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, - signer input.Signer, commitTx *wire.MsgTx, stateNum uint64) ( - *LocalForceCloseSummary, error) { + signer input.Signer, commitTx *wire.MsgTx, stateNum uint64, + 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 @@ -7221,13 +7932,32 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) + auxResult, err := fn.MapOptionZ( + leafStore, func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return s.FetchLeavesFromCommit( + NewAuxChanState(chanState), + chanState.LocalCommitment, *keyRing, + lntypes.Local, + ) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) + } + var leaseExpiry uint32 if chanState.ChanType.HasLeaseExpiration() { leaseExpiry = chanState.ThawHeight } + + localAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.LocalAuxLeaf + }, + )(auxResult.AuxLeaves) toLocalScript, err := CommitScriptToSelf( chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey, - keyRing.RevocationKey, csvTimeout, leaseExpiry, + keyRing.RevocationKey, csvTimeout, leaseExpiry, localAuxLeaf, ) if err != nil { return nil, err @@ -7306,6 +8036,36 @@ 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, //nolint:lll + ChanType: chanState.ChanType, + 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 @@ -7318,7 +8078,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local, signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, commitTx, chanState.ChanType, - chanState.IsInitiator, leaseExpiry, + chanState.IsInitiator, leaseExpiry, chanState, + auxResult.AuxLeaves, auxResolver, ) if err != nil { return nil, fmt.Errorf("unable to gen htlc resolution: %w", err) @@ -7333,19 +8094,41 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, } return &LocalForceCloseSummary{ - ChanPoint: chanState.FundingOutpoint, - CloseTx: commitTx, - CommitResolution: commitResolution, - HtlcResolutions: htlcResolutions, - ChanSnapshot: *chanState.Snapshot(), - AnchorResolution: anchorResolution, + ChanPoint: chanState.FundingOutpoint, + CloseTx: commitTx, + ChanSnapshot: *chanState.Snapshot(), + ContractResolutions: fn.Some(ContractResolutions{ + CommitResolution: commitResolution, + HtlcResolutions: htlcResolutions, + AnchorResolution: anchorResolution, + }), }, nil } +// CloseOutput wraps a normal tx out with additional metadata that indicates if +// the output belongs to the initiator of the channel or not. +type CloseOutput struct { + wire.TxOut + + // IsLocal indicates if the output belong to the local party. + IsLocal bool +} + +// CloseSortFunc is a function type alias for a function that sorts the closing +// transaction. +type CloseSortFunc func(*wire.MsgTx) error + // chanCloseOpt is a functional option that can be used to modify the co-op // close process. type chanCloseOpt struct { musigSession *MusigSession + + extraCloseOutputs []CloseOutput + + // customSort is a custom function that can be used to sort the + // transaction outputs. If this isn't set, then the default BIP-69 + // sorting is used. + customSort CloseSortFunc } // ChanCloseOpt is a closure type that cen be used to modify the set of default @@ -7366,6 +8149,22 @@ func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt { } } +// WithExtraCloseOutputs can be used to add extra outputs to the cooperative +// transaction. +func WithExtraCloseOutputs(extraOutputs []CloseOutput) ChanCloseOpt { + return func(opts *chanCloseOpt) { + opts.extraCloseOutputs = extraOutputs + } +} + +// WithCustomCoopSort can be used to modify the way the co-op close transaction +// is sorted. +func WithCustomCoopSort(sorter CloseSortFunc) ChanCloseOpt { + return func(opts *chanCloseOpt) { + opts.customSort = sorter + } +} + // CreateCloseProposal is used by both parties in a cooperative channel close // workflow to generate proposed close transactions and signatures. This method // should only be executed once all pending HTLCs (if any) on the channel have @@ -7373,9 +8172,6 @@ func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt { // the "closing" state, which indicates that all incoming/outgoing HTLC // requests should be rejected. A signature for the closing transaction is // returned. -// -// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs, -// settle any in flight. func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, localDeliveryScript []byte, remoteDeliveryScript []byte, closeOpts ...ChanCloseOpt) (input.Signature, *chainhash.Hash, @@ -7386,7 +8182,6 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // If we're already closing the channel, then ignore this request. if lc.isClosed { - // TODO(roasbeef): check to ensure no pending payments return nil, nil, 0, ErrChanClosing } @@ -7400,7 +8195,10 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // during the channel closing process. ourBalance, theirBalance, err := CoopCloseBalance( lc.channelState.ChanType, lc.channelState.IsInitiator, - proposedFee, lc.channelState.LocalCommitment, + proposedFee, + lc.channelState.LocalCommitment.LocalBalance.ToSatoshis(), + lc.channelState.LocalCommitment.RemoteBalance.ToSatoshis(), + lc.channelState.LocalCommitment.CommitFee, ) if err != nil { return nil, nil, 0, err @@ -7413,11 +8211,27 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, closeTxOpts = append(closeTxOpts, WithRBFCloseTx()) } - closeTx := CreateCooperativeCloseTx( + // If we have any extra outputs to pass along, then we'll map that to + // the co-op close option txn type. + if opts.extraCloseOutputs != nil { + closeTxOpts = append(closeTxOpts, WithExtraTxCloseOutputs( + opts.extraCloseOutputs, + )) + } + if opts.customSort != nil { + closeTxOpts = append( + closeTxOpts, WithCustomTxSort(opts.customSort), + ) + } + + closeTx, err := CreateCooperativeCloseTx( fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, localDeliveryScript, remoteDeliveryScript, closeTxOpts..., ) + if err != nil { + return nil, nil, 0, err + } // Ensure that the transaction doesn't explicitly violate any // consensus rules such as being too big, or having any value with a @@ -7482,7 +8296,10 @@ func (lc *LightningChannel) CompleteCooperativeClose( // Get the final balances after subtracting the proposed fee. ourBalance, theirBalance, err := CoopCloseBalance( lc.channelState.ChanType, lc.channelState.IsInitiator, - proposedFee, lc.channelState.LocalCommitment, + proposedFee, + lc.channelState.LocalCommitment.LocalBalance.ToSatoshis(), + lc.channelState.LocalCommitment.RemoteBalance.ToSatoshis(), + lc.channelState.LocalCommitment.CommitFee, ) if err != nil { return nil, 0, err @@ -7495,14 +8312,30 @@ func (lc *LightningChannel) CompleteCooperativeClose( closeTxOpts = append(closeTxOpts, WithRBFCloseTx()) } + // If we have any extra outputs to pass along, then we'll map that to + // the co-op close option txn type. + if opts.extraCloseOutputs != nil { + closeTxOpts = append(closeTxOpts, WithExtraTxCloseOutputs( + opts.extraCloseOutputs, + )) + } + if opts.customSort != nil { + closeTxOpts = append( + closeTxOpts, WithCustomTxSort(opts.customSort), + ) + } + // Create the transaction used to return the current settled balance // on this active channel back to both parties. In this current model, // the initiator pays full fees for the cooperative close transaction. - closeTx := CreateCooperativeCloseTx( + closeTx, err := CreateCooperativeCloseTx( fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, localDeliveryScript, remoteDeliveryScript, closeTxOpts..., ) + if err != nil { + return nil, 0, err + } // Ensure that the transaction doesn't explicitly validate any // consensus rules such as being too big, or having any value with a @@ -7726,7 +8559,7 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, WitnessScript: anchorWitnessScript, Output: &wire.TxOut{ PkScript: localAnchor.PkScript(), - Value: int64(anchorSize), + Value: int64(AnchorSize), }, HashType: sweepSigHash(chanState.ChanType), } @@ -7738,7 +8571,7 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, //nolint:lll signDesc.PrevOutputFetcher = txscript.NewCannedPrevOutputFetcher( - localAnchor.PkScript(), int64(anchorSize), + localAnchor.PkScript(), int64(AnchorSize), ) // For anchor outputs with taproot channels, the key desc is @@ -7819,9 +8652,9 @@ func (lc *LightningChannel) availableBalance( // We'll grab the current set of log updates that the remote has // ACKed. - remoteACKedIndex := lc.localCommitChain.tip().theirMessageIndex + remoteACKedIndex := lc.commitChains.Local.tip().messageIndices.Remote htlcView := lc.fetchHTLCView(remoteACKedIndex, - lc.localUpdateLog.logIndex) + lc.updateLogs.Local.logIndex) // Calculate our available balance from our local commitment. // TODO(halseth): could reuse parts validateCommitmentSanity to do this @@ -7848,13 +8681,13 @@ func (lc *LightningChannel) availableBalance( } // availableCommitmentBalance attempts to calculate the balance we have -// available for HTLCs on the local/remote commitment given the htlcView. To +// available for HTLCs on the local/remote commitment given the HtlcView. To // account for sending HTLCs of different sizes, it will report the balance // available for sending non-dust HTLCs, which will be manifested on the // commitment, increasing the commitment fee we must pay as an initiator, // eating into our balance. It will make sure we won't violate the channel // reserve constraints for this amount. -func (lc *LightningChannel) availableCommitmentBalance(view *htlcView, +func (lc *LightningChannel) availableCommitmentBalance(view *HtlcView, whoseCommitChain lntypes.ChannelParty, buffer BufferType) ( lnwire.MilliSatoshi, lntypes.WeightUnit) { @@ -7884,7 +8717,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView, // Calculate the commitment fee in the case where we would add another // HTLC to the commitment, as only the balance remaining after this fee // has been paid is actually available for sending. - feePerKw := filteredView.feePerKw + feePerKw := filteredView.FeePerKw additionalHtlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) @@ -8004,7 +8837,7 @@ func (lc *LightningChannel) validateFeeRate(feePerKw chainfee.SatPerKWeight) err availableBalance, txWeight := lc.availableBalance(AdditionalHtlc) oldFee := lnwire.NewMSatFromSatoshis( - lc.localCommitChain.tip().feePerKw.FeeForWeight(txWeight), + lc.commitChains.Local.tip().feePerKw.FeeForWeight(txWeight), ) // Our base balance is the total amount of satoshis we can commit @@ -8052,13 +8885,14 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error { return err } - pd := &PaymentDescriptor{ - LogIndex: lc.localUpdateLog.logIndex, + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), + LogIndex: lc.updateLogs.Local.logIndex, Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)), EntryType: FeeUpdate, } - lc.localUpdateLog.appendUpdate(pd) + lc.updateLogs.Local.appendUpdate(pd) return nil } @@ -8077,8 +8911,8 @@ func (lc *LightningChannel) CommitFeeTotalAt( // We want to grab every update in both update logs to calculate the // commitment fees in the worst-case with this fee-rate. - localIdx := lc.localUpdateLog.logIndex - remoteIdx := lc.remoteUpdateLog.logIndex + localIdx := lc.updateLogs.Local.logIndex + remoteIdx := lc.updateLogs.Remote.logIndex localHtlcView := lc.fetchHTLCView(remoteIdx, localIdx) @@ -8124,13 +8958,14 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er } // TODO(roasbeef): or just modify to use the other balance? - pd := &PaymentDescriptor{ - LogIndex: lc.remoteUpdateLog.logIndex, + pd := &paymentDescriptor{ + ChanID: lc.ChannelID(), + LogIndex: lc.updateLogs.Remote.logIndex, Amount: lnwire.NewMSatFromSatoshis(btcutil.Amount(feePerKw)), EntryType: FeeUpdate, } - lc.remoteUpdateLog.appendUpdate(pd) + lc.updateLogs.Remote.appendUpdate(pd) return nil } @@ -8198,6 +9033,15 @@ type closeTxOpts struct { // enableRBF indicates whether the cooperative close tx should signal // RBF or not. enableRBF bool + + // extraCloseOutputs is a set of additional outputs that should be + // added the co-op close transaction. + extraCloseOutputs []CloseOutput + + // customSort is a custom function that can be used to sort the + // transaction outputs. If this isn't set, then the default BIP-69 + // sorting is used. + customSort CloseSortFunc } // defaultCloseTxOpts returns a closeTxOpts struct with default values. @@ -8218,6 +9062,22 @@ func WithRBFCloseTx() CloseTxOpt { } } +// WithExtraTxCloseOutputs can be used to add extra outputs to the cooperative +// transaction. +func WithExtraTxCloseOutputs(extraOutputs []CloseOutput) CloseTxOpt { + return func(o *closeTxOpts) { + o.extraCloseOutputs = extraOutputs + } +} + +// WithCustomTxSort can be used to modify the way the close transaction is +// sorted. +func WithCustomTxSort(sorter CloseSortFunc) CloseTxOpt { + return func(opts *closeTxOpts) { + opts.customSort = sorter + } +} + // CreateCooperativeCloseTx creates a transaction which if signed by both // parties, then broadcast cooperatively closes an active channel. The creation // of the closure transaction is modified by a boolean indicating if the party @@ -8227,7 +9087,7 @@ func WithRBFCloseTx() CloseTxOpt { func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, localDust, remoteDust, ourBalance, theirBalance btcutil.Amount, ourDeliveryScript, theirDeliveryScript []byte, - closeOpts ...CloseTxOpt) *wire.MsgTx { + closeOpts ...CloseTxOpt) (*wire.MsgTx, error) { opts := defaultCloseTxOpts() for _, optFunc := range closeOpts { @@ -8249,28 +9109,132 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, // Create both cooperative closure outputs, properly respecting the // dust limits of both parties. - if ourBalance >= localDust { + var localOutputIdx fn.Option[int] + haveLocalOutput := ourBalance >= localDust + if haveLocalOutput { closeTx.AddTxOut(&wire.TxOut{ PkScript: ourDeliveryScript, Value: int64(ourBalance), }) + + localOutputIdx = fn.Some(len(closeTx.TxOut) - 1) } - if theirBalance >= remoteDust { + + var remoteOutputIdx fn.Option[int] + haveRemoteOutput := theirBalance >= remoteDust + if haveRemoteOutput { closeTx.AddTxOut(&wire.TxOut{ PkScript: theirDeliveryScript, Value: int64(theirBalance), }) + + remoteOutputIdx = fn.Some(len(closeTx.TxOut) - 1) + } + + // If we have extra outputs to add to the co-op close transaction, then + // we'll examine them now. We'll deduct the output's value from the + // owning party. In the case that a party can't pay for the output, then + // their normal output will be omitted. + for _, extraTxOut := range opts.extraCloseOutputs { + switch { + // For additional local outputs, add the output, then deduct + // the balance from our local balance. + case extraTxOut.IsLocal: + // The extraCloseOutputs in the options just indicate if + // an extra output should be added in general. But we + // only add one if we actually _need_ one, based on the + // balance. If we don't have enough local balance to + // cover the extra output, then localOutputIdx is None. + localOutputIdx.WhenSome(func(idx int) { + // The output that currently represents the + // local balance, which means: + // txOut.Value == ourBalance. + txOut := closeTx.TxOut[idx] + + // The extra output (if one exists) is the more + // important one, as in custom channels it might + // carry some additional values. The normal + // output is just an address that sends the + // local balance back to our wallet. The extra + // one also goes to our wallet, but might also + // carry other values, so it has higher + // priority. Do we have enough balance to have + // both the extra output with the given value + // (which is subtracted from our balance) and + // still an above-dust normal output? If not, we + // skip the extra output and just overwrite the + // existing output script with the one from the + // extra output. + amtAfterOutput := btcutil.Amount( + txOut.Value - extraTxOut.Value, + ) + if amtAfterOutput <= localDust { + txOut.PkScript = extraTxOut.PkScript + + return + } + + txOut.Value -= extraTxOut.Value + closeTx.AddTxOut(&extraTxOut.TxOut) + }) + + // For extra remote outputs, we'll do the opposite. + case !extraTxOut.IsLocal: + // The extraCloseOutputs in the options just indicate if + // an extra output should be added in general. But we + // only add one if we actually _need_ one, based on the + // balance. If we don't have enough remote balance to + // cover the extra output, then remoteOutputIdx is None. + remoteOutputIdx.WhenSome(func(idx int) { + // The output that currently represents the + // remote balance, which means: + // txOut.Value == theirBalance. + txOut := closeTx.TxOut[idx] + + // The extra output (if one exists) is the more + // important one, as in custom channels it might + // carry some additional values. The normal + // output is just an address that sends the + // remote balance back to their wallet. The + // extra one also goes to their wallet, but + // might also carry other values, so it has + // higher priority. Do they have enough balance + // to have both the extra output with the given + // value (which is subtracted from their + // balance) and still an above-dust normal + // output? If not, we skip the extra output and + // just overwrite the existing output script + // with the one from the extra output. + amtAfterOutput := btcutil.Amount( + txOut.Value - extraTxOut.Value, + ) + if amtAfterOutput <= remoteDust { + txOut.PkScript = extraTxOut.PkScript + + return + } + + txOut.Value -= extraTxOut.Value + closeTx.AddTxOut(&extraTxOut.TxOut) + }) + } } - txsort.InPlaceSort(closeTx) + if opts.customSort != nil { + if err := opts.customSort(closeTx); err != nil { + return nil, err + } + } else { + txsort.InPlaceSort(closeTx) + } - return closeTx + return closeTx, nil } // LocalBalanceDust returns true if when creating a co-op close transaction, // the balance of the local party will be dust after accounting for any anchor // outputs. -func (lc *LightningChannel) LocalBalanceDust() bool { +func (lc *LightningChannel) LocalBalanceDust() (bool, btcutil.Amount) { lc.RLock() defer lc.RUnlock() @@ -8281,16 +9245,18 @@ func (lc *LightningChannel) LocalBalanceDust() bool { // regain the stats allocated to the anchor outputs with the co-op // close transaction. if chanState.ChanType.HasAnchors() && chanState.IsInitiator { - localBalance += 2 * anchorSize + localBalance += 2 * AnchorSize } - return localBalance <= chanState.LocalChanCfg.DustLimit + localDust := chanState.LocalChanCfg.DustLimit + + return localBalance <= localDust, localDust } // RemoteBalanceDust returns true if when creating a co-op close transaction, // the balance of the remote party will be dust after accounting for any anchor // outputs. -func (lc *LightningChannel) RemoteBalanceDust() bool { +func (lc *LightningChannel) RemoteBalanceDust() (bool, btcutil.Amount) { lc.RLock() defer lc.RUnlock() @@ -8301,10 +9267,43 @@ func (lc *LightningChannel) RemoteBalanceDust() bool { // regain the stats allocated to the anchor outputs with the co-op // close transaction. if chanState.ChanType.HasAnchors() && !chanState.IsInitiator { - remoteBalance += 2 * anchorSize + remoteBalance += 2 * AnchorSize + } + + remoteDust := chanState.RemoteChanCfg.DustLimit + + return remoteBalance <= remoteDust, remoteDust +} + +// CommitBalances returns the local and remote balances in the current +// commitment state. +func (lc *LightningChannel) CommitBalances() (btcutil.Amount, btcutil.Amount) { + lc.RLock() + defer lc.RUnlock() + + chanState := lc.channelState + localCommit := lc.channelState.LocalCommitment + + localBalance := localCommit.LocalBalance.ToSatoshis() + remoteBalance := localCommit.RemoteBalance.ToSatoshis() + + if chanState.ChanType.HasAnchors() { + if chanState.IsInitiator { + localBalance += 2 * AnchorSize + } else { + remoteBalance += 2 * AnchorSize + } } - return remoteBalance <= chanState.RemoteChanCfg.DustLimit + return localBalance, remoteBalance +} + +// CommitFee returns the commitment fee for the current commitment state. +func (lc *LightningChannel) CommitFee() btcutil.Amount { + lc.RLock() + defer lc.RUnlock() + + return lc.channelState.LocalCommitment.CommitFee } // CalcFee returns the commitment fee to use for the given fee rate @@ -8337,7 +9336,7 @@ func (lc *LightningChannel) MaxFeeRate( // exactly why it was introduced to react for sharp fee changes. availableBalance, weight := lc.availableBalance(AdditionalHtlc) - currentFee := lc.localCommitChain.tip().feePerKw.FeeForWeight(weight) + currentFee := lc.commitChains.Local.tip().feePerKw.FeeForWeight(weight) // baseBalance is the maximum amount available for us to spend on fees. baseBalance := availableBalance.ToSatoshis() + currentFee @@ -8596,10 +9595,10 @@ func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi { // NOTE: remoteMessageIndex is the height on the tip because this is called // before the tail is advanced to the tip during ReceiveRevocation. func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, - localMessageIndex uint64, chanID lnwire.ChannelID) []channeldb.LogUpdate { + localMessageIndex uint64) []channeldb.LogUpdate { var localPeerUpdates []channeldb.LogUpdate - for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() { + for e := lc.updateLogs.Local.Front(); e != nil; e = e.Next() { pd := e.Value // We don't save add updates as they are restored from the @@ -8613,38 +9612,9 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, // covered in the next commitment signature that the remote // sends. if pd.LogIndex < remoteMessageIndex && pd.LogIndex >= localMessageIndex { - logUpdate := channeldb.LogUpdate{ - LogIndex: pd.LogIndex, - } - - switch pd.EntryType { - case FeeUpdate: - logUpdate.UpdateMsg = &lnwire.UpdateFee{ - ChanID: chanID, - FeePerKw: uint32(pd.Amount.ToSatoshis()), - } - case Settle: - logUpdate.UpdateMsg = &lnwire.UpdateFulfillHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - PaymentPreimage: pd.RPreimage, - } - case Fail: - logUpdate.UpdateMsg = &lnwire.UpdateFailHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - Reason: pd.FailReason, - } - case MalformedFail: - logUpdate.UpdateMsg = &lnwire.UpdateFailMalformedHTLC{ - ChanID: chanID, - ID: pd.ParentIndex, - ShaOnionBlob: pd.ShaOnionBlob, - FailureCode: pd.FailCode, - } - } - - localPeerUpdates = append(localPeerUpdates, logUpdate) + localPeerUpdates = append( + localPeerUpdates, pd.toLogUpdate(), + ) } } @@ -8703,12 +9673,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces, // TODO(roasbeef): propagate rename of signing and verification nonces sessionCfg := &MusigSessionCfg{ - LocalKey: localChanCfg.MultiSigKey, - RemoteKey: remoteChanCfg.MultiSigKey, - LocalNonce: *localNonce, - RemoteNonce: *remoteNonce, - Signer: lc.Signer, - InputTxOut: &lc.fundingOutput, + LocalKey: localChanCfg.MultiSigKey, + RemoteKey: remoteChanCfg.MultiSigKey, + LocalNonce: *localNonce, + RemoteNonce: *remoteNonce, + Signer: lc.Signer, + InputTxOut: &lc.fundingOutput, + TapscriptTweak: lc.channelState.TapscriptRoot, } lc.musigSessions = NewMusigPairSession( sessionCfg, @@ -8756,3 +9727,32 @@ func (lc *LightningChannel) MultiSigKeys() (keychain.KeyDescriptor, return lc.channelState.LocalChanCfg.MultiSigKey, lc.channelState.RemoteChanCfg.MultiSigKey } + +// LocalCommitmentBlob returns the custom blob of the local commitment. +func (lc *LightningChannel) LocalCommitmentBlob() fn.Option[tlv.Blob] { + lc.RLock() + defer lc.RUnlock() + + chanState := lc.channelState + localBalance := chanState.LocalCommitment.CustomBlob + + return fn.MapOption(func(b tlv.Blob) tlv.Blob { + newBlob := make([]byte, len(b)) + copy(newBlob, b) + + return newBlob + })(localBalance) +} + +// FundingBlob returns the funding custom blob. +func (lc *LightningChannel) FundingBlob() fn.Option[tlv.Blob] { + lc.RLock() + defer lc.RUnlock() + + return fn.MapOption(func(b tlv.Blob) tlv.Blob { + newBlob := make([]byte, len(b)) + copy(newBlob, b) + + return newBlob + })(lc.channelState.CustomBlob) +} diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 0281c59bd4..232497e255 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -2,30 +2,37 @@ package lnwallet import ( "bytes" + "context" + crand "crypto/rand" "crypto/sha256" + "errors" "fmt" "math/rand" "reflect" "runtime" "testing" "testing/quick" + "time" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -46,6 +53,18 @@ func createHTLC(id int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32] }, returnPreimage } +// addAndReceiveHTLC adds an HTLC as local to the first channel, and as remote +// to a second channel. The HTLC ID is not modified. +func addAndReceiveHTLC(t *testing.T, channel1, channel2 *LightningChannel, + htlc *lnwire.UpdateAddHTLC, openKey *models.CircuitKey) { + + _, err := channel1.AddHTLC(htlc, openKey) + require.NoErrorf(t, err, "channel 1 unable to add htlc: %v", err) + + _, err = channel2.ReceiveHTLC(htlc) + require.NoErrorf(t, err, "channel 2 unable to recv htlc: %v", err) +} + func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx, value btcutil.Amount, ) { @@ -107,7 +126,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, // we expect the messages to be ordered, Bob will receive the HTLC we // just sent before he receives this signature, so the signature will // cover the HTLC. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and checks that this covers the @@ -125,13 +144,13 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, // This signature will cover the HTLC, since Bob will first send the // revocation just created. The revocation also acks every received // HTLC up to the point where Alice sent here signature. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign alice's commitment") // Alice then processes this revocation, sending her own revocation for // her prior commitment transaction. Alice shouldn't have any HTLCs to // forward since she's sending an outgoing HTLC. - fwdPkg, _, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation) + fwdPkg, _, err := aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to process bob's revocation") if len(fwdPkg.Adds) != 0 { t.Fatalf("alice forwards %v add htlcs, should forward none", @@ -156,7 +175,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, // is fully locked in within both commitment transactions. Bob should // also be able to forward an HTLC now that the HTLC has been locked // into both commitment transactions. - fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + fwdPkg, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") if len(fwdPkg.Adds) != 1 { t.Fatalf("bob forwards %v add htlcs, should only forward one", @@ -238,17 +257,17 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign settle commitment") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") aliceRevocation2, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to generate revocation") - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign new commitment") - fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation2) + fwdPkg, _, err = bobChannel.ReceiveRevocation(aliceRevocation2) require.NoError(t, err, "bob unable to process alice's revocation") if len(fwdPkg.Adds) != 0 { t.Fatalf("bob forwards %v add htlcs, should forward none", @@ -285,7 +304,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, } } - fwdPkg, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation2) + fwdPkg, _, err = aliceChannel.ReceiveRevocation(bobRevocation2) require.NoError(t, err, "alice unable to process bob's revocation") if len(fwdPkg.Adds) != 0 { // Alice should now be able to forward the settlement HTLC to @@ -334,21 +353,23 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool, // The logs of both sides should now be cleared since the entry adding // the HTLC should have been removed once both sides receive the // revocation. - if aliceChannel.localUpdateLog.Len() != 0 { + if aliceChannel.updateLogs.Local.Len() != 0 { t.Fatalf("alice's local not updated, should be empty, has %v "+ - "entries instead", aliceChannel.localUpdateLog.Len()) + "entries instead", aliceChannel.updateLogs.Local.Len()) } - if aliceChannel.remoteUpdateLog.Len() != 0 { + if aliceChannel.updateLogs.Remote.Len() != 0 { t.Fatalf("alice's remote not updated, should be empty, has %v "+ - "entries instead", aliceChannel.remoteUpdateLog.Len()) + "entries instead", aliceChannel.updateLogs.Remote.Len()) } - if len(aliceChannel.localUpdateLog.updateIndex) != 0 { - t.Fatalf("alice's local log index not cleared, should be empty but "+ - "has %v entries", len(aliceChannel.localUpdateLog.updateIndex)) + if len(aliceChannel.updateLogs.Local.updateIndex) != 0 { + t.Fatalf("alice's local log index not cleared, should be "+ + "empty but has %v entries", + len(aliceChannel.updateLogs.Local.updateIndex)) } - if len(aliceChannel.remoteUpdateLog.updateIndex) != 0 { - t.Fatalf("alice's remote log index not cleared, should be empty but "+ - "has %v entries", len(aliceChannel.remoteUpdateLog.updateIndex)) + if len(aliceChannel.updateLogs.Remote.updateIndex) != 0 { + t.Fatalf("alice's remote log index not cleared, should be "+ + "empty but has %v entries", + len(aliceChannel.updateLogs.Remote.updateIndex)) } } @@ -386,6 +407,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { ) }) + t.Run("taproot with tapscript root", func(t *testing.T) { + flags := channeldb.SimpleTaprootFeatureBit | + channeldb.TapscriptRootBit + testAddSettleWorkflow(t, true, flags, false) + }) + t.Run("storeFinalHtlcResolutions=true", func(t *testing.T) { testAddSettleWorkflow(t, false, 0, true) }) @@ -426,10 +453,7 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { htlc, _ := createHTLC(0, lnwire.MilliSatoshi(500000)) // -----add-----> - _, err = aliceChannel.AddHTLC(htlc, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Force a state transition to lock in this add on both commitments. // -----sig-----> @@ -448,7 +472,7 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) @@ -459,7 +483,7 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) // We now restore Alice's channel as this was the point at which @@ -475,14 +499,11 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { htlc2, _ := createHTLC(0, lnwire.MilliSatoshi(500000)) // <----add----- - _, err = bobChannel.AddHTLC(htlc2, nil) - require.NoError(t, err) - _, err = newAliceChannel.ReceiveHTLC(htlc2) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, newAliceChannel, htlc2, nil) // Bob should now send a commitment signature to Alice. // <----sig----- - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) // Alice should accept the commitment. Previously she would @@ -534,12 +555,7 @@ func TestCheckCommitTxSize(t *testing.T) { for i := 0; i <= 10; i++ { htlc, _ := createHTLC(i, lnwire.MilliSatoshi(1e7)) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("bob unable to receive htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete state update: %v", err) @@ -614,19 +630,14 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { Expiry: uint32(numHtlcs - i), } - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("bob unable to receive htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) } // Have Alice initiate the first half of the commitment dance. The // tie-breaking for commitment sorting won't affect the commitment // signed by Alice because received HTLC scripts commit to the CLTV // directly, so the outputs will have different scriptPubkeys. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign alice's commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) @@ -634,14 +645,14 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob's commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive bob's revocation") // Now have Bob initiate the second half of the commitment dance. Here // the offered HTLC scripts he adds for Alice will need to have the // tie-breaking applied because the CLTV is not committed, but instead // implicit via the construction of the second-level transactions. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign bob's commitment") if len(bobNewCommit.PendingHTLCs) != numHtlcs { @@ -692,6 +703,68 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { require.NoError(t, err, "unable to receive bob's commitment") } +// TestCommitHTLCSigCustomRecordSize asserts that custom records produced for +// a commitment_signed message are properly limited in size. +func TestCommitHTLCSigCustomRecordSize(t *testing.T) { + aliceChannel, bobChannel, err := CreateTestChannels( + t, channeldb.SimpleTaprootFeatureBit| + channeldb.TapscriptRootBit, + ) + require.NoError(t, err, "unable to create test channels") + + const ( + htlcAmt = lnwire.MilliSatoshi(20000000) + numHtlcs = 2 + ) + + largeRecords := lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType: bytes.Repeat([]byte{0}, 65_500), + } + largeBlob, err := largeRecords.Serialize() + require.NoError(t, err) + + aliceChannel.auxSigner.WhenSome(func(a AuxSigner) { + mockSigner, ok := a.(*MockAuxSigner) + require.True(t, ok, "expected MockAuxSigner") + + // Replace the default PackSigs implementation to return a + // large custom records blob. + mockSigner.ExpectedCalls = fn.Filter(func(c *mock.Call) bool { + return c.Method != "PackSigs" + }, mockSigner.ExpectedCalls) + mockSigner.On("PackSigs", mock.Anything). + Return(fn.Ok(fn.Some(largeBlob))) + }) + + // Add HTLCs with identical payment hashes and amounts, but descending + // CLTV values. We will expect the signatures to appear in the reverse + // order that the HTLCs are added due to the commitment sorting. + for i := 0; i < numHtlcs; i++ { + var ( + preimage lntypes.Preimage + hash = preimage.Hash() + ) + + htlc := &lnwire.UpdateAddHTLC{ + ID: uint64(i), + PaymentHash: hash, + Amount: htlcAmt, + Expiry: uint32(numHtlcs - i), + } + + if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { + t.Fatalf("alice unable to add htlc: %v", err) + } + if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { + t.Fatalf("bob unable to receive htlc: %v", err) + } + } + + // We expect an error because of the large custom records blob. + _, err = aliceChannel.SignNextCommitment(ctxb) + require.ErrorContains(t, err, "exceeds max allowed size") +} + // TestCooperativeChannelClosure checks that the coop close process finishes // with an agreement from both parties, and that the final balances of the // close tx check out. @@ -705,7 +778,7 @@ func TestCooperativeChannelClosure(t *testing.T) { testCoopClose(t, &coopCloseTestCase{ chanType: channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit, - anchorAmt: anchorSize * 2, + anchorAmt: AnchorSize * 2, }) }) } @@ -815,7 +888,7 @@ func TestForceClose(t *testing.T) { chanType: channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit, expectedCommitWeight: input.AnchorCommitWeight, - anchorAmt: anchorSize * 2, + anchorAmt: AnchorSize * 2, }) }) t.Run("taproot", func(t *testing.T) { @@ -824,7 +897,17 @@ func TestForceClose(t *testing.T) { channeldb.AnchorOutputsBit | channeldb.SimpleTaprootFeatureBit, expectedCommitWeight: input.TaprootCommitWeight, - anchorAmt: anchorSize * 2, + anchorAmt: AnchorSize * 2, + }) + }) + t.Run("taproot with tapscript root", func(t *testing.T) { + testForceClose(t, &forceCloseTestCase{ + chanType: channeldb.SingleFunderTweaklessBit | + channeldb.AnchorOutputsBit | + channeldb.SimpleTaprootFeatureBit | + channeldb.TapscriptRootBit, + expectedCommitWeight: input.TaprootCommitWeight, + anchorAmt: AnchorSize * 2, }) }) } @@ -853,23 +936,13 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // We'll ensure that the HTLC amount is above Alice's dust limit. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) // We'll also a distinct HTLC from Bob -> Alice. This way, Alice will // have both an incoming and outgoing HTLC on her commitment // transaction. htlcBob, preimageBob := createHTLC(0, htlcAmount) - if _, err := bobChannel.AddHTLC(htlcBob, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := aliceChannel.ReceiveHTLC(htlcBob); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlcBob, nil) // Next, we'll perform two state transitions to ensure that both HTLC's // get fully locked-in. @@ -885,24 +958,26 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { closeSummary, err := aliceChannel.ForceClose() require.NoError(t, err, "unable to force close channel") + resolutionsAlice := closeSummary.ContractResolutions.UnwrapOrFail(t) + // Alice should detect that she can sweep the outgoing HTLC after a // timeout, but also that she's able to sweep in incoming HTLC Bob sent // her. - if len(closeSummary.HtlcResolutions.OutgoingHTLCs) != 1 { + if len(resolutionsAlice.HtlcResolutions.OutgoingHTLCs) != 1 { t.Fatalf("alice out htlc resolutions not populated: expected %v "+ "htlcs, got %v htlcs", - 1, len(closeSummary.HtlcResolutions.OutgoingHTLCs)) + 1, len(resolutionsAlice.HtlcResolutions.OutgoingHTLCs)) } - if len(closeSummary.HtlcResolutions.IncomingHTLCs) != 1 { + if len(resolutionsAlice.HtlcResolutions.IncomingHTLCs) != 1 { t.Fatalf("alice in htlc resolutions not populated: expected %v "+ "htlcs, got %v htlcs", - 1, len(closeSummary.HtlcResolutions.IncomingHTLCs)) + 1, len(resolutionsAlice.HtlcResolutions.IncomingHTLCs)) } // Verify the anchor resolutions for the anchor commitment format. if testCase.chanType.HasAnchors() { // Check the close summary resolution. - anchorRes := closeSummary.AnchorResolution + anchorRes := resolutionsAlice.AnchorResolution if anchorRes == nil { t.Fatal("expected anchor resolution") } @@ -910,7 +985,7 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { t.Fatal("commit tx not referenced by anchor res") } if anchorRes.AnchorSignDescriptor.Output.Value != - int64(anchorSize) { + int64(AnchorSize) { t.Fatal("unexpected anchor size") } @@ -936,7 +1011,7 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // The SelfOutputSignDesc should be non-nil since the output to-self is // non-dust. - aliceCommitResolution := closeSummary.CommitResolution + aliceCommitResolution := resolutionsAlice.CommitResolution if aliceCommitResolution == nil { t.Fatalf("alice fails to include to-self output in " + "ForceCloseSummary") @@ -982,7 +1057,7 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // Next, we'll ensure that the second level HTLC transaction it itself // spendable, and also that the delivery output (with delay) itself has // a valid sign descriptor. - htlcResolution := closeSummary.HtlcResolutions.OutgoingHTLCs[0] + htlcResolution := resolutionsAlice.HtlcResolutions.OutgoingHTLCs[0] outHtlcIndex := htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint.Index senderHtlcPkScript := closeSummary.CloseTx.TxOut[outHtlcIndex].PkScript @@ -1066,7 +1141,7 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // We'll now perform similar set of checks to ensure that Alice is able // to sweep the output that Bob sent to her on-chain with knowledge of // the preimage. - inHtlcResolution := closeSummary.HtlcResolutions.IncomingHTLCs[0] + inHtlcResolution := resolutionsAlice.HtlcResolutions.IncomingHTLCs[0] inHtlcIndex := inHtlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint.Index receiverHtlcScript := closeSummary.CloseTx.TxOut[inHtlcIndex].PkScript @@ -1147,7 +1222,8 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // Check the same for Bob's ForceCloseSummary. closeSummary, err = bobChannel.ForceClose() require.NoError(t, err, "unable to force close channel") - bobCommitResolution := closeSummary.CommitResolution + resolutionsBob := closeSummary.ContractResolutions.UnwrapOrFail(t) + bobCommitResolution := resolutionsBob.CommitResolution if bobCommitResolution == nil { t.Fatalf("bob fails to include to-self output in ForceCloseSummary") } @@ -1181,19 +1257,19 @@ func testForceClose(t *testing.T, testCase *forceCloseTestCase) { // As we didn't add the preimage of Alice's HTLC to bob's preimage // cache, he should only detect that he can sweep only his outgoing // HTLC upon force close. - if len(closeSummary.HtlcResolutions.OutgoingHTLCs) != 1 { + if len(resolutionsBob.HtlcResolutions.OutgoingHTLCs) != 1 { t.Fatalf("alice out htlc resolutions not populated: expected %v "+ "htlcs, got %v htlcs", - 1, len(closeSummary.HtlcResolutions.OutgoingHTLCs)) + 1, len(resolutionsBob.HtlcResolutions.OutgoingHTLCs)) } // Bob should recognize that the incoming HTLC is there, but the // preimage should be empty as he doesn't have the knowledge required // to sweep it. - if len(closeSummary.HtlcResolutions.IncomingHTLCs) != 1 { + if len(resolutionsBob.HtlcResolutions.IncomingHTLCs) != 1 { t.Fatalf("bob in htlc resolutions not populated: expected %v "+ "htlcs, got %v htlcs", - 1, len(closeSummary.HtlcResolutions.IncomingHTLCs)) + 1, len(resolutionsBob.HtlcResolutions.IncomingHTLCs)) } } @@ -1252,9 +1328,11 @@ func TestForceCloseDustOutput(t *testing.T) { closeSummary, err := aliceChannel.ForceClose() require.NoError(t, err, "unable to force close channel") + resolutionsAlice := closeSummary.ContractResolutions.UnwrapOrFail(t) + // Alice's to-self output should still be in the commitment // transaction. - commitResolution := closeSummary.CommitResolution + commitResolution := resolutionsAlice.CommitResolution if commitResolution == nil { t.Fatalf("alice fails to include to-self output in " + "ForceCloseSummary") @@ -1288,10 +1366,11 @@ func TestForceCloseDustOutput(t *testing.T) { closeSummary, err = bobChannel.ForceClose() require.NoError(t, err, "unable to force close channel") + resolutionsBob := closeSummary.ContractResolutions.UnwrapOrFail(t) // Bob's to-self output is below Bob's dust value and should be // reflected in the ForceCloseSummary. - commitResolution = closeSummary.CommitResolution + commitResolution = resolutionsBob.CommitResolution if commitResolution != nil { t.Fatalf("bob incorrectly includes to-self output in " + "ForceCloseSummary") @@ -1323,12 +1402,7 @@ func TestDustHTLCFees(t *testing.T) { // This HTLC amount should be lower than the dust limits of both nodes. htlcAmount := lnwire.NewMSatFromSatoshis(100) htlc, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("bob unable to receive htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } @@ -1417,12 +1491,12 @@ func TestHTLCDustLimit(t *testing.T) { // while Bob's should not, because the value falls beneath his dust // limit. The amount of the HTLC should be applied to fees in Bob's // commitment transaction. - aliceCommitment := aliceChannel.localCommitChain.tip() + aliceCommitment := aliceChannel.commitChains.Local.tip() if len(aliceCommitment.txn.TxOut) != 3 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 3, len(aliceCommitment.txn.TxOut)) } - bobCommitment := bobChannel.localCommitChain.tip() + bobCommitment := bobChannel.commitChains.Local.tip() if len(bobCommitment.txn.TxOut) != 2 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 2, len(bobCommitment.txn.TxOut)) @@ -1447,7 +1521,7 @@ func TestHTLCDustLimit(t *testing.T) { // At this point, for Alice's commitment chains, the value of the HTLC // should have been added to Alice's balance and TotalSatoshisSent. - commitment := aliceChannel.localCommitChain.tip() + commitment := aliceChannel.commitChains.Local.tip() if len(commitment.txn.TxOut) != 2 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 2, len(commitment.txn.TxOut)) @@ -1481,14 +1555,9 @@ func TestHTLCSigNumber(t *testing.T) { for i, htlcSat := range htlcValues { htlcMsat := lnwire.NewMSatFromSatoshis(htlcSat) htlc, _ := createHTLC(i, htlcMsat) - _, err := aliceChannel.AddHTLC(htlc, nil) - if err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - _, err = bobChannel.ReceiveHTLC(htlc) - if err != nil { - t.Fatalf("bob unable to receive htlc: %v", err) - } + addAndReceiveHTLC( + t, aliceChannel, bobChannel, htlc, nil, + ) } return aliceChannel, bobChannel @@ -1512,7 +1581,7 @@ func TestHTLCSigNumber(t *testing.T) { // =================================================================== aliceChannel, bobChannel := createChanWithHTLC(aboveDust, aboveDust) - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "Error signing next commitment") if len(aliceNewCommit.HtlcSigs) != 2 { @@ -1535,7 +1604,7 @@ func TestHTLCSigNumber(t *testing.T) { // =================================================================== aliceChannel, bobChannel = createChanWithHTLC(aboveDust) - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "Error signing next commitment") if len(aliceNewCommit.HtlcSigs) != 1 { @@ -1557,7 +1626,7 @@ func TestHTLCSigNumber(t *testing.T) { // ============================================================== aliceChannel, bobChannel = createChanWithHTLC(belowDust) - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "Error signing next commitment") // Since the HTLC is below Bob's dust limit, Alice won't need to send @@ -1575,7 +1644,7 @@ func TestHTLCSigNumber(t *testing.T) { // ================================================================ aliceChannel, bobChannel = createChanWithHTLC(aboveDust) - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "Error signing next commitment") // Since the HTLC is above Bob's dust limit, Alice should send a @@ -1596,7 +1665,7 @@ func TestHTLCSigNumber(t *testing.T) { // Alice should produce only one signature, since one HTLC is below // dust. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "Error signing next commitment") if len(aliceNewCommit.HtlcSigs) != 1 { @@ -1680,7 +1749,7 @@ func TestChannelBalanceDustLimit(t *testing.T) { // output for Alice's balance should have been removed as dust, leaving // only a single output that will send the remaining funds in the // channel to Bob. - commitment := bobChannel.localCommitChain.tip() + commitment := bobChannel.commitChains.Local.tip() if len(commitment.txn.TxOut) != 1 { t.Fatalf("incorrect # of outputs: expected %v, got %v", 1, len(commitment.txn.TxOut)) @@ -1722,12 +1791,7 @@ func TestStateUpdatePersistence(t *testing.T) { OnionBlob: fakeOnionBlob, } - if _, err := aliceChannel.AddHTLC(h, nil); err != nil { - t.Fatalf("unable to add alice's htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(h); err != nil { - t.Fatalf("unable to recv alice's htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, h, nil) } rHash := sha256.Sum256(bobPreimage[:]) bobh := &lnwire.UpdateAddHTLC{ @@ -1736,12 +1800,7 @@ func TestStateUpdatePersistence(t *testing.T) { Expiry: uint32(10), OnionBlob: fakeOnionBlob, } - if _, err := bobChannel.AddHTLC(bobh, nil); err != nil { - t.Fatalf("unable to add bob's htlc: %v", err) - } - if _, err := aliceChannel.ReceiveHTLC(bobh); err != nil { - t.Fatalf("unable to recv bob's htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, bobh, nil) // Also add a fee update to the update logs. fee := chainfee.SatPerKWeight(333) @@ -1755,26 +1814,26 @@ func TestStateUpdatePersistence(t *testing.T) { // Helper method that asserts the expected number of updates are found // in the update logs. assertNumLogUpdates := func(numAliceUpdates, numBobUpdates int) { - if aliceChannel.localUpdateLog.Len() != numAliceUpdates { + if aliceChannel.updateLogs.Local.Len() != numAliceUpdates { t.Fatalf("expected %d local updates, found %d", numAliceUpdates, - aliceChannel.localUpdateLog.Len()) + aliceChannel.updateLogs.Local.Len()) } - if aliceChannel.remoteUpdateLog.Len() != numBobUpdates { + if aliceChannel.updateLogs.Remote.Len() != numBobUpdates { t.Fatalf("expected %d remote updates, found %d", numBobUpdates, - aliceChannel.remoteUpdateLog.Len()) + aliceChannel.updateLogs.Remote.Len()) } - if bobChannel.localUpdateLog.Len() != numBobUpdates { + if bobChannel.updateLogs.Local.Len() != numBobUpdates { t.Fatalf("expected %d local updates, found %d", numBobUpdates, - bobChannel.localUpdateLog.Len()) + bobChannel.updateLogs.Local.Len()) } - if bobChannel.remoteUpdateLog.Len() != numAliceUpdates { + if bobChannel.updateLogs.Remote.Len() != numAliceUpdates { t.Fatalf("expected %d remote updates, found %d", numAliceUpdates, - bobChannel.remoteUpdateLog.Len()) + bobChannel.updateLogs.Remote.Len()) } } @@ -1798,25 +1857,25 @@ func TestStateUpdatePersistence(t *testing.T) { // After the state transition the fee update is fully locked in, and // should've been removed from both channels' update logs. - if aliceChannel.localCommitChain.tail().feePerKw != fee { + if aliceChannel.commitChains.Local.tail().feePerKw != fee { t.Fatalf("fee not locked in") } - if bobChannel.localCommitChain.tail().feePerKw != fee { + if bobChannel.commitChains.Local.tail().feePerKw != fee { t.Fatalf("fee not locked in") } assertNumLogUpdates(3, 1) // The latest commitment from both sides should have all the HTLCs. - numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs - numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs + numAliceOutgoing := aliceChannel.commitChains.Local.tail().outgoingHTLCs + numAliceIncoming := aliceChannel.commitChains.Local.tail().incomingHTLCs if len(numAliceOutgoing) != 3 { t.Fatalf("expected %v htlcs, instead got %v", 3, numAliceOutgoing) } if len(numAliceIncoming) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numAliceIncoming) } - numBobOutgoing := bobChannel.localCommitChain.tail().outgoingHTLCs - numBobIncoming := bobChannel.localCommitChain.tail().incomingHTLCs + numBobOutgoing := bobChannel.commitChains.Local.tail().outgoingHTLCs + numBobIncoming := bobChannel.commitChains.Local.tail().incomingHTLCs if len(numBobOutgoing) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numBobOutgoing) } @@ -1850,62 +1909,72 @@ func TestStateUpdatePersistence(t *testing.T) { // The state update logs of the new channels and the old channels // should now be identical other than the height the HTLCs were added. - if aliceChannel.localUpdateLog.logIndex != - aliceChannelNew.localUpdateLog.logIndex { + if aliceChannel.updateLogs.Local.logIndex != + aliceChannelNew.updateLogs.Local.logIndex { + t.Fatalf("alice log counter: expected %v, got %v", - aliceChannel.localUpdateLog.logIndex, - aliceChannelNew.localUpdateLog.logIndex) + aliceChannel.updateLogs.Local.logIndex, + aliceChannelNew.updateLogs.Local.logIndex) } - if aliceChannel.remoteUpdateLog.logIndex != - aliceChannelNew.remoteUpdateLog.logIndex { + if aliceChannel.updateLogs.Remote.logIndex != + aliceChannelNew.updateLogs.Remote.logIndex { + t.Fatalf("alice log counter: expected %v, got %v", - aliceChannel.remoteUpdateLog.logIndex, - aliceChannelNew.remoteUpdateLog.logIndex) + aliceChannel.updateLogs.Remote.logIndex, + aliceChannelNew.updateLogs.Remote.logIndex) } - if aliceChannel.localUpdateLog.Len() != - aliceChannelNew.localUpdateLog.Len() { + if aliceChannel.updateLogs.Local.Len() != + aliceChannelNew.updateLogs.Local.Len() { + t.Fatalf("alice log len: expected %v, got %v", - aliceChannel.localUpdateLog.Len(), - aliceChannelNew.localUpdateLog.Len()) + aliceChannel.updateLogs.Local.Len(), + aliceChannelNew.updateLogs.Local.Len()) } - if aliceChannel.remoteUpdateLog.Len() != - aliceChannelNew.remoteUpdateLog.Len() { + if aliceChannel.updateLogs.Remote.Len() != + aliceChannelNew.updateLogs.Remote.Len() { + t.Fatalf("alice log len: expected %v, got %v", - aliceChannel.remoteUpdateLog.Len(), - aliceChannelNew.remoteUpdateLog.Len()) + aliceChannel.updateLogs.Remote.Len(), + aliceChannelNew.updateLogs.Remote.Len()) } - if bobChannel.localUpdateLog.logIndex != - bobChannelNew.localUpdateLog.logIndex { + if bobChannel.updateLogs.Local.logIndex != + bobChannelNew.updateLogs.Local.logIndex { + t.Fatalf("bob log counter: expected %v, got %v", - bobChannel.localUpdateLog.logIndex, - bobChannelNew.localUpdateLog.logIndex) + bobChannel.updateLogs.Local.logIndex, + bobChannelNew.updateLogs.Local.logIndex) } - if bobChannel.remoteUpdateLog.logIndex != - bobChannelNew.remoteUpdateLog.logIndex { + if bobChannel.updateLogs.Remote.logIndex != + bobChannelNew.updateLogs.Remote.logIndex { + t.Fatalf("bob log counter: expected %v, got %v", - bobChannel.remoteUpdateLog.logIndex, - bobChannelNew.remoteUpdateLog.logIndex) + bobChannel.updateLogs.Remote.logIndex, + bobChannelNew.updateLogs.Remote.logIndex) } - if bobChannel.localUpdateLog.Len() != - bobChannelNew.localUpdateLog.Len() { + if bobChannel.updateLogs.Local.Len() != + bobChannelNew.updateLogs.Local.Len() { + t.Fatalf("bob log len: expected %v, got %v", - bobChannel.localUpdateLog.Len(), - bobChannelNew.localUpdateLog.Len()) + bobChannel.updateLogs.Local.Len(), + bobChannelNew.updateLogs.Local.Len()) } - if bobChannel.remoteUpdateLog.Len() != - bobChannelNew.remoteUpdateLog.Len() { + if bobChannel.updateLogs.Remote.Len() != + bobChannelNew.updateLogs.Remote.Len() { + t.Fatalf("bob log len: expected %v, got %v", - bobChannel.remoteUpdateLog.Len(), - bobChannelNew.remoteUpdateLog.Len()) + bobChannel.updateLogs.Remote.Len(), + bobChannelNew.updateLogs.Remote.Len()) } // TODO(roasbeef): expand test to also ensure state revocation log has // proper pk scripts // Newly generated pkScripts for HTLCs should be the same as in the old channel. - for _, entry := range aliceChannel.localUpdateLog.htlcIndex { + for _, entry := range aliceChannel.updateLogs.Local.htlcIndex { htlc := entry.Value - restoredHtlc := aliceChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex) + restoredHtlc := aliceChannelNew.updateLogs.Local.lookupHtlc( + htlc.HtlcIndex, + ) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("alice ourPkScript in ourLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) @@ -1915,9 +1984,11 @@ func TestStateUpdatePersistence(t *testing.T) { htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } - for _, entry := range aliceChannel.remoteUpdateLog.htlcIndex { + for _, entry := range aliceChannel.updateLogs.Remote.htlcIndex { htlc := entry.Value - restoredHtlc := aliceChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex) + restoredHtlc := aliceChannelNew.updateLogs.Remote.lookupHtlc( + htlc.HtlcIndex, + ) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("alice ourPkScript in theirLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) @@ -1927,9 +1998,11 @@ func TestStateUpdatePersistence(t *testing.T) { htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } - for _, entry := range bobChannel.localUpdateLog.htlcIndex { + for _, entry := range bobChannel.updateLogs.Local.htlcIndex { htlc := entry.Value - restoredHtlc := bobChannelNew.localUpdateLog.lookupHtlc(htlc.HtlcIndex) + restoredHtlc := bobChannelNew.updateLogs.Local.lookupHtlc( + htlc.HtlcIndex, + ) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("bob ourPkScript in ourLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) @@ -1939,9 +2012,11 @@ func TestStateUpdatePersistence(t *testing.T) { htlc.theirPkScript[:5], restoredHtlc.theirPkScript[:5]) } } - for _, entry := range bobChannel.remoteUpdateLog.htlcIndex { + for _, entry := range bobChannel.updateLogs.Remote.htlcIndex { htlc := entry.Value - restoredHtlc := bobChannelNew.remoteUpdateLog.lookupHtlc(htlc.HtlcIndex) + restoredHtlc := bobChannelNew.updateLogs.Remote.lookupHtlc( + htlc.HtlcIndex, + ) if !bytes.Equal(htlc.ourPkScript, restoredHtlc.ourPkScript) { t.Fatalf("bob ourPkScript in theirLog: expected %X, got %X", htlc.ourPkScript[:5], restoredHtlc.ourPkScript[:5]) @@ -2072,20 +2147,24 @@ func TestCancelHTLC(t *testing.T) { // Now HTLCs should be present on the commitment transaction for either // side. - if len(aliceChannel.localCommitChain.tip().outgoingHTLCs) != 0 || - len(aliceChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 { + if len(aliceChannel.commitChains.Local.tip().outgoingHTLCs) != 0 || + len(aliceChannel.commitChains.Remote.tip().outgoingHTLCs) != 0 { + t.Fatalf("htlc's still active from alice's POV") } - if len(aliceChannel.localCommitChain.tip().incomingHTLCs) != 0 || - len(aliceChannel.remoteCommitChain.tip().incomingHTLCs) != 0 { + if len(aliceChannel.commitChains.Local.tip().incomingHTLCs) != 0 || + len(aliceChannel.commitChains.Remote.tip().incomingHTLCs) != 0 { + t.Fatalf("htlc's still active from alice's POV") } - if len(bobChannel.localCommitChain.tip().outgoingHTLCs) != 0 || - len(bobChannel.remoteCommitChain.tip().outgoingHTLCs) != 0 { + if len(bobChannel.commitChains.Local.tip().outgoingHTLCs) != 0 || + len(bobChannel.commitChains.Remote.tip().outgoingHTLCs) != 0 { + t.Fatalf("htlc's still active from bob's POV") } - if len(bobChannel.localCommitChain.tip().incomingHTLCs) != 0 || - len(bobChannel.remoteCommitChain.tip().incomingHTLCs) != 0 { + if len(bobChannel.commitChains.Local.tip().incomingHTLCs) != 0 || + len(bobChannel.commitChains.Remote.tip().incomingHTLCs) != 0 { + t.Fatalf("htlc's still active from bob's POV") } @@ -2356,7 +2435,7 @@ func TestUpdateFeeFail(t *testing.T) { // Alice sends signature for commitment that does not cover any fee // update. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Bob verifies this commit, meaning that he checks that it is @@ -2389,12 +2468,7 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { // First Alice adds the outgoing HTLC to her local channel's state // update log. Then Alice sends this wire message over to Bob who // adds this htlc to his remote state update log. - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Simulate Alice sending update fee message to bob. fee := chainfee.SatPerKWeight(333) @@ -2403,11 +2477,11 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { } // Alice signs a commitment, and sends this to bob. - aliceNewCommits, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // At the same time, Bob signs a commitment. - bobNewCommits, err := bobChannel.SignNextCommitment() + bobNewCommits, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign alice's commitment") // ...that Alice receives. @@ -2464,12 +2538,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // First Alice adds the outgoing HTLC to her local channel's state // update log. Then Alice sends this wire message over to Bob who // adds this htlc to his remote state update log. - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Simulate Alice sending update fee message to bob. fee := chainfee.SatPerKWeight(333) @@ -2479,7 +2548,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceNewCommits, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and verifies that it is @@ -2509,13 +2578,13 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume // that Bob's received everything up to the signature she sent, // including the HTLC and fee update. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to process bob's revocation") // Alice receives new signature from Bob, and assumes this covers the @@ -2543,7 +2612,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { } // Bob receives revocation from Alice. - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") } @@ -2572,12 +2641,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // First Alice adds the outgoing HTLC to her local channel's state // update log. Then Alice sends this wire message over to Bob who // adds this htlc to his remote state update log. - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Simulate Alice sending update fee message to bob fee := chainfee.SatPerKWeight(333) @@ -2587,7 +2651,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob commits to every change he has sent since last time (none). He // does not commit to the received HTLC and fee update, since Alice // cannot know if he has received them. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Alice receives this signature message, and verifies that it is @@ -2602,13 +2666,13 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { require.NoError(t, err, "unable to generate bob revocation") // Bob receives the revocation of the old commitment - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "alice unable to process bob's revocation") // Alice will sign next commitment. Since she sent the revocation, she // also ack'ed everything received, but in this case this is nothing. // Since she sent the two updates, this signature will cover those two. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign alice's commitment") // Bob gets the signature for the new commitment from Alice. He assumes @@ -2638,12 +2702,12 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob will send a new signature, which will cover what he just acked: // the HTLC and fee update. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Alice receives revocation from Bob, and can now be sure that Bob // received the two updates, and they are considered locked in. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "bob unable to process alice's revocation") // Alice will receive the signature from Bob, which will cover what was @@ -2671,7 +2735,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { } // Bob receives revocation from Alice. - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") } @@ -2728,7 +2792,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") bobChannel.ReceiveUpdateFee(fee1) @@ -2774,13 +2838,13 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume that // Bob's received everything up to the signature she sent, including the // HTLC and fee update. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to process bob's revocation") // Alice receives new signature from Bob, and assumes this covers the @@ -2810,7 +2874,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { } // Bob receives revocation from Alice. - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") } @@ -2880,7 +2944,9 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel, } } - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceChanSyncMsg) + bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( + ctxb, aliceChanSyncMsg, + ) if err != nil { t.Fatalf("line #%v: unable to process ChannelReestablish "+ "msg: %v", line, err) @@ -2890,7 +2956,9 @@ func assertNoChanSyncNeeded(t *testing.T, aliceChannel *LightningChannel, "instead wants to send: %v", line, spew.Sdump(bobMsgsToSend)) } - aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobChanSyncMsg) + aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( + ctxb, bobChanSyncMsg, + ) if err != nil { t.Fatalf("line #%v: unable to process ChannelReestablish "+ "msg: %v", line, err) @@ -3004,6 +3072,10 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) { return channelNew, nil } +// testChanSyncOweCommitment tests that if Bob restarts (and then Alice) before +// he receives Alice's CommitSig message, then Alice concludes that she needs +// to re-send the CommitDiff. After the diff has been sent, both nodes should +// resynchronize and be able to complete the dangling commit. func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, @@ -3080,7 +3152,7 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { // Now we'll begin the core of the test itself. Alice will extend a new // commitment to Bob, but the connection drops before Bob can process // it. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // If this is a taproot channel, then we'll generate fresh verification @@ -3105,7 +3177,7 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { // above. assertAliceCommitRetransmit := func() *lnwire.CommitSig { aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( - bobSyncMsg, + ctxb, bobSyncMsg, ) if err != nil { t.Fatalf("unable to process chan sync msg: %v", err) @@ -3168,8 +3240,10 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { len(commitSigMsg.HtlcSigs)) } for i, htlcSig := range commitSigMsg.HtlcSigs { - if !bytes.Equal(htlcSig.RawBytes(), - aliceNewCommit.HtlcSigs[i].RawBytes()) { + if !bytes.Equal( + htlcSig.RawBytes(), + aliceNewCommit.HtlcSigs[i].RawBytes(), + ) { t.Fatalf("htlc sig msgs don't match: "+ "expected %v got %v", @@ -3195,7 +3269,9 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { // From Bob's Pov he has nothing else to send, so he should conclude he // has no further action remaining. - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg) + bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( + ctxb, aliceSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(bobMsgsToSend) != 0 { t.Fatalf("expected bob to send %v messages instead will "+ @@ -3223,15 +3299,15 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") // At this point, we'll now assert that their log states are what we @@ -3240,39 +3316,39 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { // Alice's local log counter should be 4 and her HTLC index 3. She // should detect Bob's remote log counter as being 3 and his HTLC index // 3 as well. - if aliceChannel.localUpdateLog.logIndex != 4 { + if aliceChannel.updateLogs.Local.logIndex != 4 { t.Fatalf("incorrect log index: expected %v, got %v", 4, - aliceChannel.localUpdateLog.logIndex) + aliceChannel.updateLogs.Local.logIndex) } - if aliceChannel.localUpdateLog.htlcCounter != 1 { + if aliceChannel.updateLogs.Local.htlcCounter != 1 { t.Fatalf("incorrect htlc index: expected %v, got %v", 1, - aliceChannel.localUpdateLog.htlcCounter) + aliceChannel.updateLogs.Local.htlcCounter) } - if aliceChannel.remoteUpdateLog.logIndex != 3 { + if aliceChannel.updateLogs.Remote.logIndex != 3 { t.Fatalf("incorrect log index: expected %v, got %v", 3, - aliceChannel.localUpdateLog.logIndex) + aliceChannel.updateLogs.Local.logIndex) } - if aliceChannel.remoteUpdateLog.htlcCounter != 3 { + if aliceChannel.updateLogs.Remote.htlcCounter != 3 { t.Fatalf("incorrect htlc index: expected %v, got %v", 3, - aliceChannel.localUpdateLog.htlcCounter) + aliceChannel.updateLogs.Local.htlcCounter) } // Bob should also have the same state, but mirrored. - if bobChannel.localUpdateLog.logIndex != 3 { + if bobChannel.updateLogs.Local.logIndex != 3 { t.Fatalf("incorrect log index: expected %v, got %v", 3, - bobChannel.localUpdateLog.logIndex) + bobChannel.updateLogs.Local.logIndex) } - if bobChannel.localUpdateLog.htlcCounter != 3 { + if bobChannel.updateLogs.Local.htlcCounter != 3 { t.Fatalf("incorrect htlc index: expected %v, got %v", 3, - bobChannel.localUpdateLog.htlcCounter) + bobChannel.updateLogs.Local.htlcCounter) } - if bobChannel.remoteUpdateLog.logIndex != 4 { + if bobChannel.updateLogs.Remote.logIndex != 4 { t.Fatalf("incorrect log index: expected %v, got %v", 4, - bobChannel.localUpdateLog.logIndex) + bobChannel.updateLogs.Local.logIndex) } - if bobChannel.remoteUpdateLog.htlcCounter != 1 { + if bobChannel.updateLogs.Remote.htlcCounter != 1 { t.Fatalf("incorrect htlc index: expected %v, got %v", 1, - bobChannel.localUpdateLog.htlcCounter) + bobChannel.updateLogs.Local.htlcCounter) } // We'll conclude the test by having Bob settle Alice's HTLC, then @@ -3340,6 +3416,249 @@ func TestChanSyncOweCommitment(t *testing.T) { } } +type testSigBlob struct { + BlobInt tlv.RecordT[tlv.TlvType65634, uint16] +} + +// TestChanSyncOweCommitmentAuxSigner tests that when one party owes a +// signature after a channel reest, if an aux signer is present, then the +// signature message sent includes the additional aux sigs as extra data. +func TestChanSyncOweCommitmentAuxSigner(t *testing.T) { + t.Parallel() + + // Create a test channel which will be used for the duration of this + // unittest. The channel will be funded evenly with Alice having 5 BTC, + // and Bob having 5 BTC. + chanType := channeldb.SingleFunderTweaklessBit | + channeldb.AnchorOutputsBit | channeldb.SimpleTaprootFeatureBit | + channeldb.TapscriptRootBit + + aliceChannel, bobChannel, err := CreateTestChannels(t, chanType) + require.NoError(t, err, "unable to create test channels") + + // We'll now manually attach an aux signer to Alice's channel. We'll + // set each aux sig job to receive an instant response. + auxSigner := NewAuxSignerMock(EmptyMockJobHandler) + aliceChannel.auxSigner = fn.Some[AuxSigner](auxSigner) + + var fakeOnionBlob [lnwire.OnionPacketSize]byte + copy( + fakeOnionBlob[:], + bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize), + ) + + // To kick things off, we'll have Alice send a single HTLC to Bob. + htlcAmt := lnwire.NewMSatFromSatoshis(20000) + var bobPreimage [32]byte + copy(bobPreimage[:], bytes.Repeat([]byte{0}, 32)) + rHash := sha256.Sum256(bobPreimage[:]) + h := &lnwire.UpdateAddHTLC{ + PaymentHash: rHash, + Amount: htlcAmt, + Expiry: uint32(10), + OnionBlob: fakeOnionBlob, + } + + _, err = aliceChannel.AddHTLC(h, nil) + require.NoError(t, err, "unable to recv bob's htlc: %v", err) + + // We'll set up the mock aux signer to expect calls to PackSigs and also + // SubmitSecondLevelSigBatch. + var sigBlobBuf bytes.Buffer + sigBlob := testSigBlob{ + BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5), + } + tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record()) + require.NoError(t, err, "unable to create tlv stream") + require.NoError(t, tlvStream.Encode(&sigBlobBuf)) + + auxSigner.On( + "SubmitSecondLevelSigBatch", mock.Anything, mock.Anything, + mock.Anything, + ).Return(nil).Twice() + auxSigner.On( + "PackSigs", mock.Anything, + ).Return( + fn.Ok(fn.Some(sigBlobBuf.Bytes())), nil, + ) + + _, err = aliceChannel.SignNextCommitment(ctxb) + require.NoError(t, err, "unable to sign commitment") + + _, err = aliceChannel.GenMusigNonces() + require.NoError(t, err, "unable to generate musig nonces") + + // Next we'll simulate a restart, by having Bob send over a chan sync + // message to Alice. + bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg() + require.NoError(t, err, "unable to produce chan sync msg") + + aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( + ctxb, bobSyncMsg, + ) + require.NoError(t, err) + require.Len(t, aliceMsgsToSend, 2) + + // The first message should be an update add HTLC. + require.IsType(t, &lnwire.UpdateAddHTLC{}, aliceMsgsToSend[0]) + + // The second should be a commit sig message. + sigMsg, ok := aliceMsgsToSend[1].(*lnwire.CommitSig) + require.True(t, ok) + require.True(t, sigMsg.PartialSig.IsSome()) + + // The signature should have the CustomRecords field set. + require.NotEmpty(t, sigMsg.CustomRecords) +} + +// TestAuxSignerShutdown tests that the channel state machine gracefully handles +// a failure of the aux signer when signing a new commitment. +func TestAuxSignerShutdown(t *testing.T) { + t.Parallel() + + // We'll kick off the test by creating our channels which both are + // loaded with 5 BTC each. + aliceChannel, bobChannel, err := CreateTestChannels( + t, channeldb.SingleFunderTweaklessBit, + ) + require.NoError(t, err, "unable to create test channels") + + auxSignerShutdownErr := errors.New("aux signer shutdown") + + // We know that aux sig jobs will be checked in SignNextCommitment() in + // ascending output index order. So we'll fail on the first job that is + // out of order, i.e. with an output index greater than its position in + // the submitted jobs slice. If the jobs are ordered, we'll fail on the + // job that is at the middle of the submitted job slice. + failAuxSigJob := func(jobs []AuxSigJob) { + for idx, sigJob := range jobs { + // Simulate a clean shutdown of the aux signer and send + // an error. Skip all remaining jobs. + isMiddleJob := idx == len(jobs)/2 + if int(sigJob.OutputIndex) > idx || isMiddleJob { + sigJob.Resp <- AuxSigJobResp{ + Err: auxSignerShutdownErr, + } + + return + } + + // If the job is 'in order', send a response with no + // error. + sigJob.Resp <- AuxSigJobResp{} + } + } + + auxSigner := NewAuxSignerMock(failAuxSigJob) + aliceChannel.auxSigner = fn.Some[AuxSigner](auxSigner) + + // Each HTLC amount is 0.01 BTC. + htlcAmt := lnwire.NewMSatFromSatoshis(0.01 * btcutil.SatoshiPerBitcoin) + + // Create enough HTLCs to create multiple sig jobs (one job per HTLC). + const numHTLCs = 24 + + // Send the specified number of HTLCs. + for i := 0; i < numHTLCs; i++ { + htlc, _ := createHTLC(i, htlcAmt) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) + } + + // We'll set up the mock aux signer to expect calls to PackSigs and also + // SubmitSecondLevelSigBatch. The direct return values for this mock aux + // signer are nil. The expected error comes from the sig jobs being + // passed to failAuxSigJob above, which mimics a faulty aux signer. + var sigBlobBuf bytes.Buffer + sigBlob := testSigBlob{ + BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5), + } + tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record()) + require.NoError(t, err, "unable to create tlv stream") + require.NoError(t, tlvStream.Encode(&sigBlobBuf)) + + auxSigner.On( + "SubmitSecondLevelSigBatch", mock.Anything, mock.Anything, + mock.Anything, + ).Return(nil).Twice() + auxSigner.On( + "PackSigs", mock.Anything, + ).Return( + fn.Some(sigBlobBuf.Bytes()), nil, + ) + + _, err = aliceChannel.SignNextCommitment(ctxb) + require.ErrorIs(t, err, auxSignerShutdownErr) +} + +// TestQuitDuringSignNextCommitment tests that the channel state machine can +// successfully exit on receiving a quit signal when signing a new commitment. +func TestQuitDuringSignNextCommitment(t *testing.T) { + t.Parallel() + + // We'll kick off the test by creating our channels which both are + // loaded with 5 BTC each. + aliceChannel, bobChannel, err := CreateTestChannels( + t, channeldb.SingleFunderTweaklessBit, + ) + require.NoError(t, err, "unable to create test channels") + + // We'll simulate an aux signer that was started successfully, but is + // now frozen / inactive. This could happen if the aux signer shut down + // without sending an error on any aux sig job error channel. + noopAuxSigJob := func(jobs []AuxSigJob) {} + + auxSigner := NewAuxSignerMock(noopAuxSigJob) + aliceChannel.auxSigner = fn.Some[AuxSigner](auxSigner) + + // Each HTLC amount is 0.01 BTC. + htlcAmt := lnwire.NewMSatFromSatoshis(0.01 * btcutil.SatoshiPerBitcoin) + + // Create enough HTLCs to create multiple sig jobs (one job per HTLC). + const numHTLCs = 24 + + // Send the specified number of HTLCs. + for i := 0; i < numHTLCs; i++ { + htlc, _ := createHTLC(i, htlcAmt) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) + } + + // We'll set up the mock aux signer to expect calls to PackSigs and also + // SubmitSecondLevelSigBatch. The direct return values for this mock aux + // signer are nil. The expected error comes from the behavior of + // noopAuxSigJob above, which mimics a faulty aux signer. + var sigBlobBuf bytes.Buffer + sigBlob := testSigBlob{ + BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5), + } + tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record()) + require.NoError(t, err, "unable to create tlv stream") + require.NoError(t, tlvStream.Encode(&sigBlobBuf)) + + auxSigner.On( + "SubmitSecondLevelSigBatch", mock.Anything, mock.Anything, + mock.Anything, + ).Return(nil).Twice() + auxSigner.On( + "PackSigs", mock.Anything, + ).Return( + fn.Some(sigBlobBuf.Bytes()), nil, + ) + + quitDelay := time.Millisecond * 20 + quit, quitFunc := context.WithCancel(context.Background()) + + // Alice's channel will be stuck waiting for aux sig job responses until + // we send the quit signal. We add an explicit sleep here so that we can + // cause a failure if we run the test with a very short timeout. + go func() { + time.Sleep(quitDelay) + quitFunc() + }() + + _, err = aliceChannel.SignNextCommitment(quit) + require.ErrorIs(t, err, errQuit) +} + func testChanSyncOweCommitmentPendingRemote(t *testing.T, chanType channeldb.ChannelType, ) { @@ -3349,7 +3668,10 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, require.NoError(t, err, "unable to create test channels") var fakeOnionBlob [lnwire.OnionPacketSize]byte - copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize)) + copy( + fakeOnionBlob[:], + bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize), + ) // We'll start off the scenario where Bob send two htlcs to Alice in a // single state update. @@ -3388,7 +3710,9 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, // Next, Alice settles the HTLCs from Bob in distinct state updates. for i := 0; i < numHtlcs; i++ { - err = aliceChannel.SettleHTLC(preimages[i], uint64(i), nil, nil, nil) + err = aliceChannel.SettleHTLC( + preimages[i], uint64(i), nil, nil, nil, + ) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } @@ -3397,7 +3721,9 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, t.Fatalf("unable to settle htlc: %v", err) } - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment( + ctxb, + ) if err != nil { t.Fatalf("unable to sign commitment: %v", err) } @@ -3417,7 +3743,7 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, t.Fatalf("unable to revoke commitment: %v", err) } - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevoke) + _, _, err = aliceChannel.ReceiveRevocation(bobRevoke) if err != nil { t.Fatalf("unable to revoke commitment: %v", err) } @@ -3435,7 +3761,7 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, } // Bob signs the commitment he owes. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // This commitment is expected to contain no htlcs anymore. @@ -3453,7 +3779,7 @@ func testChanSyncOweCommitmentPendingRemote(t *testing.T, if err != nil { t.Fatal(err) } - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevoke) + _, _, err = bobChannel.ReceiveRevocation(aliceRevoke) if err != nil { t.Fatal(err) } @@ -3537,17 +3863,17 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { // // Alice signs the next state, then Bob receives and sends his // revocation message. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") @@ -3580,7 +3906,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { t.Helper() aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( - bobSyncMsg, + ctxb, bobSyncMsg, ) if err != nil { t.Fatalf("unable to process chan sync msg: %v", err) @@ -3611,7 +3937,9 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { } // From Bob's PoV he shouldn't think that he owes Alice any messages. - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg) + bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( + ctxb, aliceSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(bobMsgsToSend) != 0 { t.Fatalf("expected bob to not retransmit, instead has: %v", @@ -3642,7 +3970,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { // We'll continue by then allowing bob to process Alice's revocation // message. - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") // Finally, Alice will add an HTLC over her own such that we assert the @@ -3656,12 +3984,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { Amount: htlcAmt, Expiry: uint32(10), } - if _, err := aliceChannel.AddHTLC(aliceHtlc, nil); err != nil { - t.Fatalf("unable to add alice's htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(aliceHtlc); err != nil { - t.Fatalf("unable to recv alice's htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, aliceHtlc, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete alice's state transition: %v", err) } @@ -3671,7 +3994,7 @@ func testChanSyncOweRevocation(t *testing.T, chanType channeldb.ChannelType) { } // TestChanSyncOweRevocation tests that if Bob restarts (and then Alice) before -// he receiver's Alice's RevokeAndAck message, then Alice concludes that she +// he received Alice's RevokeAndAck message, then Alice concludes that she // needs to re-send the RevokeAndAck. After the revocation has been sent, both // nodes should be able to successfully complete another state transition. func TestChanSyncOweRevocation(t *testing.T) { @@ -3736,7 +4059,7 @@ func testChanSyncOweRevocationAndCommit(t *testing.T, // Progressing the exchange: Alice will send her signature, Bob will // receive, send a revocation and also a signature for Alice's state. - aliceNewCommits, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") @@ -3745,7 +4068,7 @@ func testChanSyncOweRevocationAndCommit(t *testing.T, // reach Alice before the connection dies. bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") // If we now attempt to resync, then Alice should conclude that she @@ -3767,7 +4090,9 @@ func testChanSyncOweRevocationAndCommit(t *testing.T, } } - aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( + ctxb, bobSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(aliceMsgsToSend) != 0 { t.Fatalf("expected alice to not retransmit, instead she's "+ @@ -3778,7 +4103,7 @@ func testChanSyncOweRevocationAndCommit(t *testing.T, t.Helper() bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( - aliceSyncMsg, + ctxb, aliceSyncMsg, ) if err != nil { t.Fatalf("unable to process chan sync msg: %v", err) @@ -3859,13 +4184,13 @@ func testChanSyncOweRevocationAndCommit(t *testing.T, // We'll now finish the state transition by having Alice process both // messages, and send her final revocation. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to recv bob's commitment") aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") } @@ -3932,13 +4257,10 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, Expiry: uint32(10), ID: 1, } - _, err = bobChannel.AddHTLC(bobHtlc[1], nil) - require.NoError(t, err, "unable to add bob's htlc") - _, err = aliceChannel.ReceiveHTLC(bobHtlc[1]) - require.NoError(t, err, "unable to recv bob's htlc") + addAndReceiveHTLC(t, bobChannel, aliceChannel, bobHtlc[1], nil) // Bob signs the new state update, and sends the signature to Alice. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) @@ -3950,7 +4272,7 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, // local commit chain getting height > remote commit chain. aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") // Next, Alice will settle that incoming HTLC, then we'll start the @@ -3962,7 +4284,7 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, // Progressing the exchange: Alice will send her signature, with Bob // processing the new state locally. - aliceNewCommits, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") @@ -3992,7 +4314,9 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, } } - aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( + ctxb, bobSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(aliceMsgsToSend) != 0 { t.Fatalf("expected alice to not retransmit, instead she's "+ @@ -4003,7 +4327,9 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, // send his RevokeAndAck message again. Additionally, the CommitSig // message that he sends should be sufficient to finalize the state // transition. - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg) + bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( + ctxb, aliceSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(bobMsgsToSend) != 2 { t.Fatalf("expected bob to send %v messages, instead "+ @@ -4082,7 +4408,7 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, // Now, we'll continue the exchange, sending Bob's revocation and // signature message to Alice, ending with Alice sending her revocation // message to Bob. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(&CommitSigs{ CommitSig: bobSigMsg.CommitSig, @@ -4092,7 +4418,7 @@ func testChanSyncOweRevocationAndCommitForceTransition(t *testing.T, require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") } @@ -4157,14 +4483,7 @@ func TestChanSyncFailure(t *testing.T) { } index++ - _, err := bobChannel.AddHTLC(bobHtlc, nil) - if err != nil { - t.Fatalf("unable to add bob's htlc: %v", err) - } - _, err = aliceChannel.ReceiveHTLC(bobHtlc) - if err != nil { - t.Fatalf("unable to recv bob's htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, bobHtlc, nil) err = ForceStateTransition(bobChannel, aliceChannel) if err != nil { t.Fatalf("unable to complete bob's state "+ @@ -4190,16 +4509,11 @@ func TestChanSyncFailure(t *testing.T) { } index++ - _, err := bobChannel.AddHTLC(bobHtlc, nil) - if err != nil { - t.Fatalf("unable to add bob's htlc: %v", err) - } - _, err = aliceChannel.ReceiveHTLC(bobHtlc) - if err != nil { - t.Fatalf("unable to recv bob's htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, bobHtlc, nil) - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment( + ctxb, + ) if err != nil { t.Fatalf("unable to sign next commit: %v", err) } @@ -4224,7 +4538,7 @@ func TestChanSyncFailure(t *testing.T) { } // Alice should detect from Bob's message that she lost state. - _, _, _, err = aliceOld.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceOld.ProcessChanSyncMsg(ctxb, bobSyncMsg) if _, ok := err.(*ErrCommitSyncLocalDataLoss); !ok { t.Fatalf("wrong error, expected "+ "ErrCommitSyncLocalDataLoss instead got: %v", @@ -4232,7 +4546,9 @@ func TestChanSyncFailure(t *testing.T) { } // Bob should detect that Alice probably lost state. - _, _, _, err = bobChannel.ProcessChanSyncMsg(aliceSyncMsg) + _, _, _, err = bobChannel.ProcessChanSyncMsg( + ctxb, aliceSyncMsg, + ) if err != ErrCommitSyncRemoteDataLoss { t.Fatalf("wrong error, expected "+ "ErrCommitSyncRemoteDataLoss instead got: %v", @@ -4293,7 +4609,7 @@ func TestChanSyncFailure(t *testing.T) { bobSyncMsg, err := bobChannel.channelState.ChanSyncMsg() require.NoError(t, err, "unable to produce chan sync msg") bobSyncMsg.LocalUnrevokedCommitPoint = nil - _, _, _, err = aliceOld.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceOld.ProcessChanSyncMsg(ctxb, bobSyncMsg) if err != ErrCannotSyncCommitChains { t.Fatalf("wrong error, expected ErrCannotSyncCommitChains "+ "instead got: %v", err) @@ -4305,7 +4621,7 @@ func TestChanSyncFailure(t *testing.T) { bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg() require.NoError(t, err, "unable to produce chan sync msg") bobSyncMsg.NextLocalCommitHeight++ - _, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceChannel.ProcessChanSyncMsg(ctxb, bobSyncMsg) if err != ErrCannotSyncCommitChains { t.Fatalf("wrong error, expected ErrCannotSyncCommitChains "+ "instead got: %v", err) @@ -4316,7 +4632,7 @@ func TestChanSyncFailure(t *testing.T) { bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg() require.NoError(t, err, "unable to produce chan sync msg") bobSyncMsg.NextLocalCommitHeight-- - _, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceChannel.ProcessChanSyncMsg(ctxb, bobSyncMsg) if err != ErrCommitSyncRemoteDataLoss { t.Fatalf("wrong error, expected ErrCommitSyncRemoteDataLoss "+ "instead got: %v", err) @@ -4332,7 +4648,7 @@ func TestChanSyncFailure(t *testing.T) { require.NoError(t, err, "unable to parse pubkey") bobSyncMsg.LocalUnrevokedCommitPoint = modCommitPoint - _, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceChannel.ProcessChanSyncMsg(ctxb, bobSyncMsg) if err != ErrInvalidLocalUnrevokedCommitPoint { t.Fatalf("wrong error, expected "+ "ErrInvalidLocalUnrevokedCommitPoint instead got: %v", @@ -4352,7 +4668,7 @@ func TestChanSyncFailure(t *testing.T) { bobSyncMsg, err = bobChannel.channelState.ChanSyncMsg() require.NoError(t, err, "unable to produce chan sync msg") bobSyncMsg.LocalUnrevokedCommitPoint = modCommitPoint - _, _, _, err = aliceChannel.ProcessChanSyncMsg(bobSyncMsg) + _, _, _, err = aliceChannel.ProcessChanSyncMsg(ctxb, bobSyncMsg) if err != ErrInvalidLocalUnrevokedCommitPoint { t.Fatalf("wrong error, expected "+ "ErrInvalidLocalUnrevokedCommitPoint instead got: %v", @@ -4419,7 +4735,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get her signature. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // Restart both channels to simulate a connection restart. @@ -4437,7 +4753,9 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { require.NoError(t, err, "unable to produce chan sync msg") // Bob should detect that he doesn't need to send anything to Alice. - bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg(aliceSyncMsg) + bobMsgsToSend, _, _, err := bobChannel.ProcessChanSyncMsg( + ctxb, aliceSyncMsg, + ) require.NoError(t, err, "unable to process chan sync msg") if len(bobMsgsToSend) != 0 { t.Fatalf("expected bob to send %v messages instead "+ @@ -4449,7 +4767,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // that she needs to first send a new UpdateFee message, and also a // CommitSig. aliceMsgsToSend, _, _, err := aliceChannel.ProcessChanSyncMsg( - bobSyncMsg, + ctxb, bobSyncMsg, ) require.NoError(t, err, "unable to process chan sync msg") if len(aliceMsgsToSend) != 2 { @@ -4505,15 +4823,15 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") // Both parties should now have the latest fee rate locked-in. @@ -4540,12 +4858,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { Amount: lnwire.NewMSatFromSatoshis(20000), Expiry: uint32(10), } - if _, err := bobChannel.AddHTLC(bobHtlc, nil); err != nil { - t.Fatalf("unable to add bob's htlc: %v", err) - } - if _, err := aliceChannel.ReceiveHTLC(bobHtlc); err != nil { - t.Fatalf("unable to recv bob's htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, bobHtlc, nil) if err := ForceStateTransition(bobChannel, aliceChannel); err != nil { t.Fatalf("unable to complete bob's state transition: %v", err) } @@ -4584,7 +4897,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { t.Helper() expUpd := expFee + expAdd - upd, fees := countLog(aliceChannel.localUpdateLog) + upd, fees := countLog(aliceChannel.updateLogs.Local) if upd != expUpd { t.Fatalf("expected %d updates, found %d in Alice's "+ "log", expUpd, upd) @@ -4593,7 +4906,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { t.Fatalf("expected %d fee updates, found %d in "+ "Alice's log", expFee, fees) } - upd, fees = countLog(bobChannel.remoteUpdateLog) + upd, fees = countLog(bobChannel.updateLogs.Remote) if upd != expUpd { t.Fatalf("expected %d updates, found %d in Bob's log", expUpd, upd) @@ -4617,12 +4930,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { var htlcs []*lnwire.UpdateAddHTLC for i := 0; i < numHTLCs; i++ { htlc, _ := createHTLC(i, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) htlcs = append(htlcs, htlc) if i%5 != 0 { @@ -4646,7 +4954,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get the signature. - aliceNewCommitSig, err := aliceChannel.SignNextCommitment() + aliceNewCommitSig, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // Before restarting Alice, to mimic the old format, we fetch the @@ -4703,15 +5011,15 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommitSigs, err := bobChannel.SignNextCommitment() + bobNewCommitSigs, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "bob unable to sign commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobNewCommitSigs.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") // Both parties should now have the latest fee rate locked-in. @@ -4731,12 +5039,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // Finally, to trigger a compactLogs execution, we'll add a new HTLC, // then force a state transition. htlc, _ := createHTLC(numHTLCs, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete bob's state transition: %v", err) } @@ -4784,11 +5087,11 @@ func TestChanSyncUnableToSync(t *testing.T) { NextLocalCommitHeight: 1000, RemoteCommitTailHeight: 9000, } - _, _, _, err = bobChannel.ProcessChanSyncMsg(badChanSync) + _, _, _, err = bobChannel.ProcessChanSyncMsg(ctxb, badChanSync) if err != ErrCannotSyncCommitChains { t.Fatalf("expected error instead have: %v", err) } - _, _, _, err = aliceChannel.ProcessChanSyncMsg(badChanSync) + _, _, _, err = aliceChannel.ProcessChanSyncMsg(ctxb, badChanSync) if err != ErrCannotSyncCommitChains { t.Fatalf("expected error instead have: %v", err) } @@ -4830,12 +5133,7 @@ func TestChanSyncInvalidLastSecret(t *testing.T) { Amount: htlcAmt, Expiry: uint32(5), } - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Then we'll initiate a state transition to lock in this new HTLC. if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { @@ -4861,7 +5159,7 @@ func TestChanSyncInvalidLastSecret(t *testing.T) { // Alice's former self should conclude that she possibly lost data as // Bob is sending a valid commit secret for the latest state. - _, _, _, err = aliceOld.ProcessChanSyncMsg(bobChanSync) + _, _, _, err = aliceOld.ProcessChanSyncMsg(ctxb, bobChanSync) if _, ok := err.(*ErrCommitSyncLocalDataLoss); !ok { t.Fatalf("wrong error, expected ErrCommitSyncLocalDataLoss "+ "instead got: %v", err) @@ -4869,7 +5167,7 @@ func TestChanSyncInvalidLastSecret(t *testing.T) { // Bob should conclude that he should force close the channel, as Alice // cannot continue operation. - _, _, _, err = bobChannel.ProcessChanSyncMsg(aliceChanSync) + _, _, _, err = bobChannel.ProcessChanSyncMsg(ctxb, aliceChanSync) if err != ErrInvalidLastCommitSecret { t.Fatalf("wrong error, expected ErrInvalidLastCommitSecret, "+ "instead got: %v", err) @@ -4957,12 +5255,7 @@ func TestChanAvailableBandwidth(t *testing.T) { alicePreimages := make([][32]byte, numHtlcs) for i := 0; i < numHtlcs; i++ { htlc, preImage := createHTLC(i, dustAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) alicePreimages[i] = preImage } @@ -4975,12 +5268,7 @@ func TestChanAvailableBandwidth(t *testing.T) { htlcAmt := lnwire.NewMSatFromSatoshis(30000) for i := 0; i < numHtlcs; i++ { htlc, preImage := createHTLC(numHtlcs+i, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) alicePreimages = append(alicePreimages, preImage) } @@ -5246,16 +5534,10 @@ func TestChanCommitWeightDustHtlcs(t *testing.T) { // Helper method to add an HTLC from Alice to Bob. htlcIndex := uint64(0) addHtlc := func(htlcAmt lnwire.MilliSatoshi) lntypes.Preimage { - t.Helper() - - htlc, preImage := createHTLC(int(htlcIndex), htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + t.Helper() + htlc, preImage := createHTLC(int(htlcIndex), htlcAmt) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete alice's state "+ "transition: %v", err) @@ -5288,9 +5570,11 @@ func TestChanCommitWeightDustHtlcs(t *testing.T) { // When sending htlcs we enforce the feebuffer on the commitment // transaction. remoteCommitWeight := func(lc *LightningChannel) lntypes.WeightUnit { - remoteACKedIndex := lc.localCommitChain.tip().theirMessageIndex + remoteACKedIndex := + lc.commitChains.Local.tip().messageIndices.Remote + htlcView := lc.fetchHTLCView(remoteACKedIndex, - lc.localUpdateLog.logIndex) + lc.updateLogs.Local.logIndex) _, w := lc.availableCommitmentBalance( htlcView, lntypes.Remote, FeeBuffer, @@ -5366,7 +5650,7 @@ func TestSignCommitmentFailNotLockedIn(t *testing.T) { // If we now try to initiate a state update, then it should fail as // Alice is unable to actually create a new state. - _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment(ctxb) if err != ErrNoWindow { t.Fatalf("expected ErrNoWindow, instead have: %v", err) } @@ -5388,23 +5672,14 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // a state transition. var htlcAmt lnwire.MilliSatoshi = 100000 htlc, _ := createHTLC(0, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) + htlc2, _ := createHTLC(1, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc2, nil); err != nil { - t.Fatalf("unable to add htlc2: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc2); err != nil { - t.Fatalf("unable to recv htlc2: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc2, nil) // We'll now manually initiate a state transition between Alice and // bob. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5418,7 +5693,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { } // Alice should detect that she doesn't need to forward any HTLC's. - fwdPkg, _, _, _, err := aliceChannel.ReceiveRevocation(bobRevocation) + fwdPkg, _, err := aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { t.Fatal(err) } @@ -5433,7 +5708,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, have Bob initiate a transition to lock in the Adds sent by // Alice. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5449,7 +5724,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Bob should now detect that he now has 2 incoming HTLC's that he can // forward along. - fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + fwdPkg, _, err = bobChannel.ReceiveRevocation(aliceRevocation) if err != nil { t.Fatal(err) } @@ -5478,7 +5753,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // We'll now initiate another state transition, but this time Bob will // lead. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5494,7 +5769,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // At this point, Bob receives the revocation from Alice, which is now // his signal to examine all the HTLC's that have been locked in to // process. - fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + fwdPkg, _, err = bobChannel.ReceiveRevocation(aliceRevocation) if err != nil { t.Fatal(err) } @@ -5513,7 +5788,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, begin another state transition led by Alice, and fail the second // HTLC part-way through the dance. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5543,22 +5818,31 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Alice should detect that she doesn't need to forward any Adds's, but // that the Fail has been locked in an can be forwarded. - _, adds, settleFails, _, err := aliceChannel.ReceiveRevocation(bobRevocation) + fwdPkg, _, err = aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { t.Fatal(err) } + + adds := fwdPkg.Adds + settleFails := fwdPkg.SettleFails if len(adds) != 0 { t.Fatalf("alice shouldn't forward any HTLC's, instead wants to "+ - "forward %v htlcs", len(adds)) + "forward %v htlcs", len(fwdPkg.Adds)) } if len(settleFails) != 1 { t.Fatalf("alice should only forward %d HTLC's, instead wants to "+ - "forward %v htlcs", 1, len(settleFails)) + "forward %v htlcs", 1, len(fwdPkg.SettleFails)) } - if settleFails[0].ParentIndex != htlc.ID { + + fail, ok := settleFails[0].UpdateMsg.(*lnwire.UpdateFailHTLC) + if !ok { + t.Fatalf("expected UpdateFailHTLC, got %T", + settleFails[0].UpdateMsg) + } + if fail.ID != htlc.ID { t.Fatalf("alice should forward fail for htlcid=%d, instead "+ "forwarding id=%d", htlc.ID, - settleFails[0].ParentIndex) + fail.ID) } // We'll now restart both Alice and Bob. This emulates a reconnection @@ -5577,7 +5861,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Have Alice initiate a state transition, which does not include the // HTLCs just re-added to the channel state. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5592,7 +5876,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Alice should detect that she doesn't need to forward any HTLC's, as // the updates haven't been committed by Bob yet. - fwdPkg, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + fwdPkg, _, err = aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { t.Fatal(err) } @@ -5606,7 +5890,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { } // Now initiate a final update from Bob to lock in the final Fail. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5623,7 +5907,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Bob should detect that he has nothing to forward, as he hasn't // received any HTLCs. - fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + fwdPkg, _, err = bobChannel.ReceiveRevocation(aliceRevocation) if err != nil { t.Fatal(err) } @@ -5638,7 +5922,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Finally, have Bob initiate a state transition that locks in the Fail // added after the restart. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5653,10 +5937,13 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // When Alice receives the revocation, she should detect that she // can now forward the freshly locked-in Fail. - _, adds, settleFails, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + fwdPkg, _, err = aliceChannel.ReceiveRevocation(bobRevocation) if err != nil { t.Fatal(err) } + + adds = fwdPkg.Adds + settleFails = fwdPkg.SettleFails if len(adds) != 0 { t.Fatalf("alice shouldn't forward any HTLC's, instead wants to "+ "forward %v htlcs", len(adds)) @@ -5665,10 +5952,16 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { t.Fatalf("alice should only forward one HTLC, instead wants to "+ "forward %v htlcs", len(settleFails)) } - if settleFails[0].ParentIndex != htlc2.ID { + + fail, ok = settleFails[0].UpdateMsg.(*lnwire.UpdateFailHTLC) + if !ok { + t.Fatalf("expected UpdateFailHTLC, got %T", + settleFails[0].UpdateMsg) + } + if fail.ID != htlc2.ID { t.Fatalf("alice should forward fail for htlcid=%d, instead "+ "forwarding id=%d", htlc2.ID, - settleFails[0].ParentIndex) + fail.ID) } } @@ -5688,15 +5981,10 @@ func TestInvalidCommitSigError(t *testing.T) { // Alice to Bob. var htlcAmt lnwire.MilliSatoshi = 100000 htlc, _ := createHTLC(0, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Alice will now attempt to initiate a state transition. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign new commit") // Before the signature gets to Bob, we'll mutate it, such that the @@ -5738,19 +6026,9 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { // initiating enough state transitions to lock both of them in. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) htlcBob, preimageBob := createHTLC(0, htlcAmount) - if _, err := bobChannel.AddHTLC(htlcBob, nil); err != nil { - t.Fatalf("bob unable to add htlc: %v", err) - } - if _, err := aliceChannel.ReceiveHTLC(htlcBob); err != nil { - t.Fatalf("alice unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlcBob, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("Can't update the channel state: %v", err) } @@ -5776,6 +6054,8 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { spendDetail, aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -5893,16 +6173,11 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { // create a new state transition. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) // With the HTLC added, we'll now manually initiate a state transition // from Alice to Bob. - _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment(ctxb) if err != nil { t.Fatal(err) } @@ -5910,7 +6185,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { // At this point, Alice's commitment chain should have a new pending // commit for Bob. We'll extract it so we can simulate Bob broadcasting // the commitment due to an issue. - bobCommit := aliceChannel.remoteCommitChain.tip().txn + bobCommit := aliceChannel.commitChains.Remote.tip().txn bobTxHash := bobCommit.TxHash() spendDetail := &chainntnfs.SpendDetail{ SpenderTxHash: &bobTxHash, @@ -5925,6 +6200,8 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { spendDetail, aliceChannel.channelState.RemoteCommitment, aliceChannel.channelState.RemoteCurrentRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -5942,6 +6219,8 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { spendDetail, aliceRemoteChainTip.Commitment, aliceChannel.channelState.RemoteNextRevocation, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err, "unable to create alice close summary") @@ -6090,12 +6369,7 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // Send the maximum allowed number of HTLCs. for i := 0; i < numHTLCs; i++ { htlc, _ := createHTLC(i, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Just assign htlcID to the last received HTLC. htlcID = htlc.ID @@ -6134,15 +6408,10 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // failed. We use numHTLCs here since the previous AddHTLC with this index // failed. htlc, _ = createHTLC(numHTLCs, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Add a commitment to Bob's commitment chain. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign next commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to recv new commitment") @@ -6215,12 +6484,7 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { // Send the maximum allowed number of HTLCs minus one. for i := 0; i < numHTLCs-1; i++ { htlc, _ := createHTLC(i, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Just assign htlcID to the last received HTLC. htlcID = htlc.ID @@ -6232,12 +6496,7 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { // Send an HTLC to Bob so that Bob's commitment transaction is full. htlc, _ := createHTLC(numHTLCs-1, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Fail back an HTLC and sign a commitment as in steps 1 & 2. err = bobChannel.FailHTLC(htlcID, []byte{}, nil, nil, nil) @@ -6247,7 +6506,7 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { t.Fatalf("unable to receive fail htlc: %v", err) } - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign next commitment") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) @@ -6255,7 +6514,7 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { // Cover the HTLC referenced with id equal to numHTLCs-1 with a new // signature (step 3). - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign next commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) @@ -6265,27 +6524,22 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke revocation") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke revocation") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "unable to receive revocation") // Send the final Add which should succeed as in step 6. htlc, _ = createHTLC(numHTLCs, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Receiving the commitment should succeed as in step 7 since space was // made. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign next commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) @@ -6322,12 +6576,7 @@ func TestMaxPendingAmount(t *testing.T) { htlcAmt := lnwire.NewMSatFromSatoshis(1.5 * btcutil.SatoshiPerBitcoin) for i := 0; i < numHTLCs; i++ { htlc, _ := createHTLC(i, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) } // We finally add one more HTLC of 0.1 BTC to Alice's commitment. This @@ -6429,12 +6678,7 @@ func TestChanReserve(t *testing.T) { htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin) htlc, _ := createHTLC(aliceIndex, htlcAmt) aliceIndex++ - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Force a state transition, making sure this HTLC is considered valid // even though the channel reserves are not met. @@ -6481,12 +6725,7 @@ func TestChanReserve(t *testing.T) { // The first HTLC should successfully be sent. htlc, _ = createHTLC(aliceIndex, htlcAmt) aliceIndex++ - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Add a second HTLC of 1 BTC. This should fail because it will take // Alice's balance all the way down to her channel reserve, but since @@ -6552,12 +6791,7 @@ func TestChanReserve(t *testing.T) { htlcAmt = lnwire.NewMSatFromSatoshis(1 * btcutil.SatoshiPerBitcoin) htlc, _ = createHTLC(bobIndex, htlcAmt) bobIndex++ - if _, err := bobChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := aliceChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc, nil) // Do a last state transition, which should succeed. if err := ForceStateTransition(bobChannel, aliceChannel); err != nil { @@ -6685,12 +6919,7 @@ func TestMinHTLC(t *testing.T) { // ErrBelowMinHTLC. htlcAmt := lnwire.NewMSatFromSatoshis(0.5 * btcutil.SatoshiPerBitcoin) htlc, _ := createHTLC(0, htlcAmt) - if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { - t.Fatalf("unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { - t.Fatalf("unable to recv htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // We add an HTLC below the min value, this should result in // an ErrBelowMinHTLC error. @@ -6822,6 +7051,8 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { breachTx := aliceChannel.channelState.RemoteCommitment.CommitTx 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") @@ -6833,8 +7064,8 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { } } -// compareHtlcs compares two PaymentDescriptors. -func compareHtlcs(htlc1, htlc2 *PaymentDescriptor) error { +// compareHtlcs compares two paymentDescriptors. +func compareHtlcs(htlc1, htlc2 *paymentDescriptor) error { if htlc1.LogIndex != htlc2.LogIndex { return fmt.Errorf("htlc log index did not match") } @@ -6852,7 +7083,7 @@ func compareHtlcs(htlc1, htlc2 *PaymentDescriptor) error { } // compareIndexes is a helper method to compare two index maps. -func compareIndexes(a, b map[uint64]*fn.Node[*PaymentDescriptor]) error { +func compareIndexes(a, b map[uint64]*fn.Node[*paymentDescriptor]) error { for k1, e1 := range a { e2, ok := b[k1] if !ok { @@ -6930,15 +7161,10 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // Bob's commit, but not on Alice's. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) // Let Alice sign a new state, which will include the HTLC just sent. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // Bob receives this commitment signature, and revokes his old state. @@ -6952,7 +7178,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // sent. However her local commitment chain still won't include the // state with the HTLC, since she hasn't received a new commitment // signature from Bob yet. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") // Now make Alice send and sign an additional HTLC. We don't let Bob @@ -6968,7 +7194,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // and remote commit chains are updated in an async fashion. Since the // remote chain was updated with the latest state (since Bob sent the // revocation earlier) we can keep advancing the remote commit chain. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // After Alice has signed this commitment, her local commitment will @@ -6993,20 +7219,20 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // compare all the logs between the old and new channels, to make sure // they all got restored properly. - err = compareLogs(aliceChannel.localUpdateLog, - newAliceChannel.localUpdateLog) + err = compareLogs(aliceChannel.updateLogs.Local, + newAliceChannel.updateLogs.Local) require.NoError(t, err, "alice local log not restored") - err = compareLogs(aliceChannel.remoteUpdateLog, - newAliceChannel.remoteUpdateLog) + err = compareLogs(aliceChannel.updateLogs.Remote, + newAliceChannel.updateLogs.Remote) require.NoError(t, err, "alice remote log not restored") - err = compareLogs(bobChannel.localUpdateLog, - newBobChannel.localUpdateLog) + err = compareLogs(bobChannel.updateLogs.Local, + newBobChannel.updateLogs.Local) require.NoError(t, err, "bob local log not restored") - err = compareLogs(bobChannel.remoteUpdateLog, - newBobChannel.remoteUpdateLog) + err = compareLogs(bobChannel.updateLogs.Remote, + newBobChannel.updateLogs.Remote) require.NoError(t, err, "bob remote log not restored") } @@ -7038,10 +7264,10 @@ func assertInLog(t *testing.T, log *updateLog, numAdds, numFails int) { // assertInLogs asserts that the expected number of Adds and Fails occurs in // the local and remote update log of the given channel. func assertInLogs(t *testing.T, channel *LightningChannel, numAddsLocal, - numFailsLocal, numAddsRemote, numFailsRemote int, -) { - assertInLog(t, channel.localUpdateLog, numAddsLocal, numFailsLocal) - assertInLog(t, channel.remoteUpdateLog, numAddsRemote, numFailsRemote) + numFailsLocal, numAddsRemote, numFailsRemote int) { + + assertInLog(t, channel.updateLogs.Local, numAddsLocal, numFailsLocal) + assertInLog(t, channel.updateLogs.Remote, numAddsRemote, numFailsRemote) } // restoreAndAssert creates a new LightningChannel from the given channel's @@ -7056,8 +7282,10 @@ func restoreAndAssert(t *testing.T, channel *LightningChannel, numAddsLocal, ) require.NoError(t, err, "unable to create new channel") - assertInLog(t, newChannel.localUpdateLog, numAddsLocal, numFailsLocal) - assertInLog(t, newChannel.remoteUpdateLog, numAddsRemote, numFailsRemote) + assertInLog(t, newChannel.updateLogs.Local, numAddsLocal, numFailsLocal) + assertInLog( + t, newChannel.updateLogs.Remote, numAddsRemote, numFailsRemote, + ) } // TestChannelRestoreUpdateLogsFailedHTLC runs through a scenario where an @@ -7113,7 +7341,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { restoreAndAssert(t, aliceChannel, 1, 0, 0, 0) // Bob sends a signature. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") @@ -7126,7 +7354,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") // At this point Alice has advanced her local commitment chain to a @@ -7140,7 +7368,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { // Now send a signature from Alice. This will give Bob a new commitment // where the HTLC is removed. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") @@ -7156,7 +7384,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { // the corresponding Fail from the local update log. bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") assertInLogs(t, aliceChannel, 0, 0, 0, 0) @@ -7177,11 +7405,7 @@ func TestDuplicateFailRejection(t *testing.T) { // parties. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - _, err = bobChannel.ReceiveHTLC(htlcAlice) - require.NoError(t, err, "unable to recv htlc") + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete state update: %v", err) @@ -7207,7 +7431,7 @@ func TestDuplicateFailRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -7244,11 +7468,7 @@ func TestDuplicateSettleRejection(t *testing.T) { // parties. htlcAmount := lnwire.NewMSatFromSatoshis(20000) htlcAlice, alicePreimage := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - _, err = bobChannel.ReceiveHTLC(htlcAlice) - require.NoError(t, err, "unable to recv htlc") + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) if err := ForceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to complete state update: %v", err) @@ -7274,7 +7494,7 @@ func TestDuplicateSettleRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -7319,18 +7539,20 @@ func TestChannelRestoreCommitHeight(t *testing.T) { t.Fatalf("unable to create new channel: %v", err) } - var pd *PaymentDescriptor + var pd *paymentDescriptor if remoteLog { - if newChannel.localUpdateLog.lookupHtlc(htlcIndex) != nil { + h := newChannel.updateLogs.Local.lookupHtlc(htlcIndex) + if h != nil { t.Fatalf("htlc found in wrong log") } - pd = newChannel.remoteUpdateLog.lookupHtlc(htlcIndex) + pd = newChannel.updateLogs.Remote.lookupHtlc(htlcIndex) } else { - if newChannel.remoteUpdateLog.lookupHtlc(htlcIndex) != nil { + h := newChannel.updateLogs.Remote.lookupHtlc(htlcIndex) + if h != nil { t.Fatalf("htlc found in wrong log") } - pd = newChannel.localUpdateLog.lookupHtlc(htlcIndex) + pd = newChannel.updateLogs.Local.lookupHtlc(htlcIndex) } if pd == nil { t.Fatalf("htlc not found in log") @@ -7350,15 +7572,10 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // We'll send an HtLC from Alice to Bob. htlcAmount := lnwire.NewMSatFromSatoshis(100000000) htlcAlice, _ := createHTLC(0, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) // Let Alice sign a new state, which will include the HTLC just sent. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // The HTLC should only be on the pending remote commitment, so the @@ -7379,7 +7596,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 1, 0) // Alice receives the revocation, ACKing her pending commitment. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") // However, the HTLC is still not locked into her local commitment, so @@ -7390,7 +7607,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Now let Bob send the commitment signature making the HTLC lock in on // Alice's commitment. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // At this stage Bob has a pending remote commitment. Make sure @@ -7408,7 +7625,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { t, aliceChannel, false, 0, 1, 1, ) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "unable to receive revocation") // Alice ACKing Bob's pending commitment shouldn't change the heights @@ -7419,16 +7636,11 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // existing HTLCs (the HTLC with index 0) keep getting the add heights // restored properly. htlcAlice, _ = createHTLC(1, htlcAmount) - if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { - t.Fatalf("alice unable to add htlc: %v", err) - } - if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { - t.Fatalf("bob unable to recv add htlc: %v", err) - } + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlcAlice, nil) // Send a new signature from Alice to Bob, making Alice have a pending // remote commitment. - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // A restoration should keep the add heights iof the first HTLC, and @@ -7453,7 +7665,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 0) // Alice receives the revocation, ACKing her pending commitment for Bob. - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") // Alice receiving Bob's revocation should bump both addCommitHeightRemote @@ -7467,7 +7679,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Sign a new state for Alice, making Bob have a pending remote // commitment. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // The signing of a new commitment for Alice should have given the new @@ -7491,7 +7703,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Bob receives the revocation, which should set both addCommitHeightRemote // fields to 2. - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "unable to receive revocation") bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 2, 2) @@ -7504,7 +7716,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { require.NoError(t, err, "unable to recv htlc cancel") // Now Bob signs for the fail update. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // Bob has a pending commitment for Alice, it shouldn't affect the add @@ -7562,13 +7774,13 @@ func TestForceCloseBorkedState(t *testing.T) { // Do the commitment dance until Bob sends a revocation so Alice is // able to receive the revocation, and then also make a new state // herself. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commit") err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") revokeMsg, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commit") err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") @@ -7594,15 +7806,15 @@ func TestForceCloseBorkedState(t *testing.T) { // At this point, all channel mutating methods should now fail as they // shouldn't be able to proceed if the channel is borked. - _, _, _, _, err = aliceChannel.ReceiveRevocation(revokeMsg) + _, _, err = aliceChannel.ReceiveRevocation(revokeMsg) if err != channeldb.ErrChanBorked { t.Fatalf("advance commitment tail should have failed") } // We manually advance the commitment tail here since the above // ReceiveRevocation call will fail before it's actually advanced. - aliceChannel.remoteCommitChain.advanceTail() - _, err = aliceChannel.SignNextCommitment() + aliceChannel.commitChains.Remote.advanceTail() + _, err = aliceChannel.SignNextCommitment(ctxb) if err != channeldb.ErrChanBorked { t.Fatalf("sign commitment should have failed: %v", err) } @@ -7816,7 +8028,7 @@ func TestIdealCommitFeeRate(t *testing.T) { maxFeeAlloc float64, ) chainfee.SatPerKWeight { balance, weight := c.availableBalance(AdditionalHtlc) - feeRate := c.localCommitChain.tip().feePerKw + feeRate := c.commitChains.Local.tip().feePerKw currentFee := feeRate.FeeForWeight(weight) maxBalance := balance.ToSatoshis() + currentFee @@ -7833,7 +8045,7 @@ func TestIdealCommitFeeRate(t *testing.T) { // currentFeeRate calculates the current fee rate of the channel. The // ideal fee rate is floored at the current fee rate of the channel. currentFeeRate := func(c *LightningChannel) chainfee.SatPerKWeight { - return c.localCommitChain.tip().feePerKw + return c.commitChains.Local.tip().feePerKw } // testCase definies the test cases when calculating the ideal fee rate @@ -8068,7 +8280,7 @@ func TestChannelFeeRateFloor(t *testing.T) { } // Check that alice can still sign commitments. - aliceNewCommit, err := alice.SignNextCommitment() + aliceNewCommit, err := alice.SignNextCommitment(ctxb) require.NoError(t, err, "alice unable to sign commitment") // Check that bob can still receive commitments. @@ -8085,8 +8297,8 @@ func TestFetchParent(t *testing.T) { name string whoseCommitChain lntypes.ChannelParty whoseUpdateLog lntypes.ChannelParty - localEntries []*PaymentDescriptor - remoteEntries []*PaymentDescriptor + localEntries []*paymentDescriptor + remoteEntries []*paymentDescriptor // parentIndex is the parent index of the entry that we will // lookup with fetch parent. @@ -8120,7 +8332,7 @@ func TestFetchParent(t *testing.T) { { name: "remote log + chain, remote add height 0", localEntries: nil, - remoteEntries: []*PaymentDescriptor{ + remoteEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8142,7 +8354,7 @@ func TestFetchParent(t *testing.T) { }, { name: "remote log, local chain, local add height 0", - remoteEntries: []*PaymentDescriptor{ + remoteEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8165,7 +8377,7 @@ func TestFetchParent(t *testing.T) { }, { name: "local log + chain, local add height 0", - localEntries: []*PaymentDescriptor{ + localEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8189,7 +8401,7 @@ func TestFetchParent(t *testing.T) { { name: "local log + remote chain, remote add height 0", - localEntries: []*PaymentDescriptor{ + localEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8213,7 +8425,7 @@ func TestFetchParent(t *testing.T) { { name: "remote log found", localEntries: nil, - remoteEntries: []*PaymentDescriptor{ + remoteEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8236,7 +8448,7 @@ func TestFetchParent(t *testing.T) { }, { name: "local log found", - localEntries: []*PaymentDescriptor{ + localEntries: []*paymentDescriptor{ // This entry will be added at log index =0. { HtlcIndex: 1, @@ -8267,20 +8479,22 @@ func TestFetchParent(t *testing.T) { // Create a lightning channel with newly initialized // local and remote logs. lc := LightningChannel{ - localUpdateLog: newUpdateLog(0, 0), - remoteUpdateLog: newUpdateLog(0, 0), + updateLogs: lntypes.Dual[*updateLog]{ + Local: newUpdateLog(0, 0), + Remote: newUpdateLog(0, 0), + }, } // Add the local and remote entries to update logs. for _, entry := range test.localEntries { - lc.localUpdateLog.appendHtlc(entry) + lc.updateLogs.Local.appendHtlc(entry) } for _, entry := range test.remoteEntries { - lc.remoteUpdateLog.appendHtlc(entry) + lc.updateLogs.Remote.appendHtlc(entry) } parent, err := lc.fetchParent( - &PaymentDescriptor{ + &paymentDescriptor{ ParentIndex: test.parentIndex, }, test.whoseCommitChain, @@ -8343,8 +8557,8 @@ func TestEvaluateView(t *testing.T) { tests := []struct { name string - ourHtlcs []*PaymentDescriptor - theirHtlcs []*PaymentDescriptor + ourHtlcs []*paymentDescriptor + theirHtlcs []*paymentDescriptor whoseCommitChain lntypes.ChannelParty mutateState bool @@ -8376,7 +8590,7 @@ func TestEvaluateView(t *testing.T) { name: "our fee update is applied", whoseCommitChain: lntypes.Local, mutateState: false, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { Amount: ourFeeUpdateAmt, EntryType: FeeUpdate, @@ -8393,8 +8607,8 @@ func TestEvaluateView(t *testing.T) { name: "their fee update is applied", whoseCommitChain: lntypes.Local, mutateState: false, - ourHtlcs: []*PaymentDescriptor{}, - theirHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{}, + theirHtlcs: []*paymentDescriptor{ { Amount: theirFeeUpdateAmt, EntryType: FeeUpdate, @@ -8411,14 +8625,14 @@ func TestEvaluateView(t *testing.T) { name: "htlcs adds without settles", whoseCommitChain: lntypes.Local, mutateState: false, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, EntryType: Add, }, }, - theirHtlcs: []*PaymentDescriptor{ + theirHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8445,7 +8659,7 @@ func TestEvaluateView(t *testing.T) { name: "our htlc settled, state mutated", whoseCommitChain: lntypes.Local, mutateState: true, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8453,7 +8667,7 @@ func TestEvaluateView(t *testing.T) { addCommitHeightLocal: addHeight, }, }, - theirHtlcs: []*PaymentDescriptor{ + theirHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8480,7 +8694,7 @@ func TestEvaluateView(t *testing.T) { name: "our htlc settled, state not mutated", whoseCommitChain: lntypes.Local, mutateState: false, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8488,7 +8702,7 @@ func TestEvaluateView(t *testing.T) { addCommitHeightLocal: addHeight, }, }, - theirHtlcs: []*PaymentDescriptor{ + theirHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8515,7 +8729,7 @@ func TestEvaluateView(t *testing.T) { name: "their htlc settled, state mutated", whoseCommitChain: lntypes.Local, mutateState: true, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8530,7 +8744,7 @@ func TestEvaluateView(t *testing.T) { ParentIndex: 1, }, }, - theirHtlcs: []*PaymentDescriptor{ + theirHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8559,7 +8773,7 @@ func TestEvaluateView(t *testing.T) { whoseCommitChain: lntypes.Local, mutateState: false, - ourHtlcs: []*PaymentDescriptor{ + ourHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8574,7 +8788,7 @@ func TestEvaluateView(t *testing.T) { ParentIndex: 0, }, }, - theirHtlcs: []*PaymentDescriptor{ + theirHtlcs: []*paymentDescriptor{ { HtlcIndex: 0, Amount: htlcAddAmount, @@ -8603,30 +8817,32 @@ func TestEvaluateView(t *testing.T) { }, // Create update logs for local and remote. - localUpdateLog: newUpdateLog(0, 0), - remoteUpdateLog: newUpdateLog(0, 0), + updateLogs: lntypes.Dual[*updateLog]{ + Local: newUpdateLog(0, 0), + Remote: newUpdateLog(0, 0), + }, } for _, htlc := range test.ourHtlcs { if htlc.EntryType == Add { - lc.localUpdateLog.appendHtlc(htlc) + lc.updateLogs.Local.appendHtlc(htlc) } else { - lc.localUpdateLog.appendUpdate(htlc) + lc.updateLogs.Local.appendUpdate(htlc) } } for _, htlc := range test.theirHtlcs { if htlc.EntryType == Add { - lc.remoteUpdateLog.appendHtlc(htlc) + lc.updateLogs.Remote.appendHtlc(htlc) } else { - lc.remoteUpdateLog.appendUpdate(htlc) + lc.updateLogs.Remote.appendUpdate(htlc) } } - view := &htlcView{ - ourUpdates: test.ourHtlcs, - theirUpdates: test.theirHtlcs, - feePerKw: feePerKw, + view := &HtlcView{ + OurUpdates: test.ourHtlcs, + TheirUpdates: test.theirHtlcs, + FeePerKw: feePerKw, } var ( @@ -8647,17 +8863,17 @@ func TestEvaluateView(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - if result.feePerKw != test.expectedFee { + if result.FeePerKw != test.expectedFee { t.Fatalf("expected fee: %v, got: %v", - test.expectedFee, result.feePerKw) + test.expectedFee, result.FeePerKw) } checkExpectedHtlcs( - t, result.ourUpdates, test.ourExpectedHtlcs, + t, result.OurUpdates, test.ourExpectedHtlcs, ) checkExpectedHtlcs( - t, result.theirUpdates, test.theirExpectedHtlcs, + t, result.TheirUpdates, test.theirExpectedHtlcs, ) if lc.channelState.TotalMSatSent != test.expectSent { @@ -8679,9 +8895,9 @@ func TestEvaluateView(t *testing.T) { // checkExpectedHtlcs checks that a set of htlcs that we have contains all the // htlcs we expect. -func checkExpectedHtlcs(t *testing.T, actual []*PaymentDescriptor, - expected map[uint64]bool, -) { +func checkExpectedHtlcs(t *testing.T, actual []*paymentDescriptor, + expected map[uint64]bool) { + if len(expected) != len(actual) { t.Fatalf("expected: %v htlcs, got: %v", len(expected), len(actual)) @@ -8871,7 +9087,7 @@ func TestProcessFeeUpdate(t *testing.T) { // Create a fee update with add and remove heights as // set in the test. heights := test.startHeights - update := &PaymentDescriptor{ + update := &paymentDescriptor{ Amount: ourFeeUpdateAmt, addCommitHeightRemote: heights.remoteAdd, addCommitHeightLocal: heights.localAdd, @@ -8880,15 +9096,15 @@ func TestProcessFeeUpdate(t *testing.T) { EntryType: FeeUpdate, } - view := &htlcView{ - feePerKw: chainfee.SatPerKWeight(feePerKw), + view := &HtlcView{ + FeePerKw: chainfee.SatPerKWeight(feePerKw), } processFeeUpdate( update, nextHeight, test.whoseCommitChain, test.mutate, view, ) - if view.feePerKw != test.expectedFee { + if view.FeePerKw != test.expectedFee { t.Fatalf("expected fee: %v, got: %v", test.expectedFee, feePerKw) } @@ -8898,7 +9114,7 @@ func TestProcessFeeUpdate(t *testing.T) { } } -func checkHeights(t *testing.T, update *PaymentDescriptor, expected heights) { +func checkHeights(t *testing.T, update *paymentDescriptor, expected heights) { updateHeights := heights{ localAdd: update.addCommitHeightLocal, localRemove: update.removeCommitHeightLocal, @@ -9267,7 +9483,7 @@ func TestProcessAddRemoveEntry(t *testing.T) { t.Parallel() heights := test.startHeights - update := &PaymentDescriptor{ + update := &paymentDescriptor{ Amount: updateAmount, addCommitHeightLocal: heights.localAdd, addCommitHeightRemote: heights.remoteAdd, @@ -9352,10 +9568,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { htlc, _ := createHTLC(0, lnwire.MilliSatoshi(500000)) // -----add-----> - _, err = aliceChannel.AddHTLC(htlc, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Force a state transition to lock in this add on both commitments. // -----sig-----> @@ -9374,7 +9587,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9383,13 +9596,13 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // -----rev-----> aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) // Alice should sign the next commitment and go down before // sending it. // -----sig-----X - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) newAliceChannel, err := NewLightningChannel( @@ -9408,22 +9621,19 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // <----rev------ bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = newAliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = newAliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) // Now Bob sends an HTLC to Alice. htlc2, _ := createHTLC(0, lnwire.MilliSatoshi(500000)) // <----add------ - _, err = bobChannel.AddHTLC(htlc2, nil) - require.NoError(t, err) - _, err = newAliceChannel.ReceiveHTLC(htlc2) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, newAliceChannel, htlc2, nil) // Bob sends the final signature to Alice and Alice should not // reject it, given that we properly restore the unsigned acked // updates and therefore our update log is structured correctly. - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9462,10 +9672,7 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { htlc, _ := createHTLC(0, lnwire.MilliSatoshi(500000)) // <----add----- - _, err = bobChannel.AddHTLC(htlc, nil) - require.NoError(t, err) - _, err = aliceChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc, nil) // Force a state transition to lock in this add on both commitments. // <----sig----- @@ -9484,7 +9691,7 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Alice should send a commitment signature to Bob. // -----sig----> - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9494,7 +9701,7 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // <----rev----- bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) // Restart Alice and assert that she can receive Bob's next commitment @@ -9508,7 +9715,7 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Bob sends the final signature and Alice should not reject it. // <----sig----- - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9549,10 +9756,7 @@ func TestChannelSignedAckRegression(t *testing.T) { htlc, preimage := createHTLC(0, lnwire.MilliSatoshi(5000000)) // <----add------ - _, err = bobChannel.AddHTLC(htlc, nil) - require.NoError(t, err) - _, err = aliceChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc, nil) // Force a state transition to lock in the HTLC. // <----sig------ @@ -9570,7 +9774,7 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----sig----> - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9578,11 +9782,11 @@ func TestChannelSignedAckRegression(t *testing.T) { // <----rev----- bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) // <----sig----- - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9591,13 +9795,10 @@ func TestChannelSignedAckRegression(t *testing.T) { htlc2, _ := createHTLC(0, lnwire.MilliSatoshi(5000000)) // -----add----> - _, err = aliceChannel.AddHTLC(htlc2, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc2) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc2, nil) // -----sig----> - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9605,7 +9806,7 @@ func TestChannelSignedAckRegression(t *testing.T) { // <----rev----- bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) // Restart Bob's channel state here. @@ -9618,7 +9819,7 @@ func TestChannelSignedAckRegression(t *testing.T) { // -----rev----> aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - fwdPkg, _, _, _, err := newBobChannel.ReceiveRevocation(aliceRevocation) + fwdPkg, _, err := newBobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) // Assert that the fwdpkg is not empty. @@ -9627,7 +9828,7 @@ func TestChannelSignedAckRegression(t *testing.T) { // Bob should no longer fail to sign this commitment due to faulty // update logs. // <----sig----- - bobNewCommit, err = newBobChannel.SignNextCommitment() + bobNewCommit, err = newBobChannel.SignNextCommitment(ctxb) require.NoError(t, err) // Alice should receive the new commitment without hiccups. @@ -9691,17 +9892,14 @@ func TestIsChannelClean(t *testing.T) { // sends an htlc. // ---add---> htlc, preimage := createHTLC(0, lnwire.MilliSatoshi(5000000)) - _, err = aliceChannel.AddHTLC(htlc, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) assertCleanOrDirty(false, aliceChannel, bobChannel, t) // Assert that the channel remains dirty until the HTLC is completely // removed from both commitments. // ---sig---> - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9710,12 +9908,12 @@ func TestIsChannelClean(t *testing.T) { // <---rev--- bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9724,7 +9922,7 @@ func TestIsChannelClean(t *testing.T) { // ---rev---> aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9736,7 +9934,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9745,12 +9943,12 @@ func TestIsChannelClean(t *testing.T) { // ---rev---> aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9759,7 +9957,7 @@ func TestIsChannelClean(t *testing.T) { // <---rev--- bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) assertCleanOrDirty(true, aliceChannel, bobChannel, t) @@ -9774,7 +9972,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceNewCommit, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9783,12 +9981,12 @@ func TestIsChannelClean(t *testing.T) { // <---rev--- bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobNewCommit, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) @@ -9798,7 +9996,7 @@ func TestIsChannelClean(t *testing.T) { // ---rev---> aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) assertCleanOrDirty(true, aliceChannel, bobChannel, t) } @@ -9872,10 +10070,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { htlc1Amt := lnwire.MilliSatoshi(700_000) htlc1, preimage1 := createHTLC(0, htlc1Amt) - _, err = bobChannel.AddHTLC(htlc1, nil) - require.NoError(t, err) - _, err = aliceChannel.ReceiveHTLC(htlc1) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc1, nil) // Assert that GetDustSum from Alice's perspective does not consider // the HTLC dust on her commitment, but does on Bob's commitment. @@ -9915,10 +10110,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { htlc2Amt := lnwire.MilliSatoshi(100_000) htlc2, _ := createHTLC(0, htlc2Amt) - _, err = aliceChannel.AddHTLC(htlc2, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc2) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc2, nil) // Assert that GetDustSum from Alice's perspective includes the new // HTLC as dust on both commitments. @@ -9929,7 +10121,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { checkDust(bobChannel, htlc2Amt, htlc2Amt) // Alice signs for this HTLC and neither perspective should change. - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) @@ -9941,20 +10133,20 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { // dust. bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) checkDust(aliceChannel, htlc2Amt, htlc2Amt) checkDust(bobChannel, htlc2Amt, htlc2Amt) // The rest of the dance is completed and neither perspective should // change. - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) + _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) checkDust(aliceChannel, htlc2Amt, htlc2Amt) checkDust(bobChannel, htlc2Amt, htlc2Amt) @@ -9966,10 +10158,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { htlc3Amt := lnwire.MilliSatoshi(400_000) htlc3, _ := createHTLC(1, htlc3Amt) - _, err = aliceChannel.AddHTLC(htlc3, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc3) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc3, nil) // Assert that this new HTLC is not counted on Alice's local commitment // in the dust sum. Bob's commitment should count it. @@ -10036,15 +10225,17 @@ func TestCreateHtlcRetribution(t *testing.T) { aliceChannel.channelState, ) htlc := &channeldb.HTLCEntry{ - Amt: testAmt, - Incoming: true, - OutputIndex: 1, + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(testAmt), + ), + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true), + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2, uint16](1), } // Create the htlc retribution. hr, err := createHtlcRetribution( aliceChannel.channelState, keyRing, commitHash, - dummyPrivate, leaseExpiry, htlc, + dummyPrivate, leaseExpiry, htlc, fn.None[CommitAuxLeaves](), ) // Expect no error. require.NoError(t, err) @@ -10052,8 +10243,8 @@ func TestCreateHtlcRetribution(t *testing.T) { // Check the fields have expected values. require.EqualValues(t, testAmt, hr.SignDesc.Output.Value) require.Equal(t, commitHash, hr.OutPoint.Hash) - require.EqualValues(t, htlc.OutputIndex, hr.OutPoint.Index) - require.Equal(t, htlc.Incoming, hr.IsIncoming) + require.EqualValues(t, htlc.OutputIndex.Val, hr.OutPoint.Index) + require.Equal(t, htlc.Incoming.Val, hr.IsIncoming) } // TestCreateBreachRetribution checks that `createBreachRetribution` behaves as @@ -10093,30 +10284,31 @@ func TestCreateBreachRetribution(t *testing.T) { aliceChannel.channelState, ) htlc := &channeldb.HTLCEntry{ - Amt: btcutil.Amount(testAmt), - Incoming: true, - OutputIndex: uint16(htlcIndex), + Amt: tlv.NewRecordT[tlv.TlvType4]( + tlv.NewBigSizeT(btcutil.Amount(testAmt)), + ), + Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](true), + OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2]( + uint16(htlcIndex), + ), } // Create a dummy revocation log. ourAmtMsat := lnwire.MilliSatoshi(ourAmt * 1000) theirAmtMsat := lnwire.MilliSatoshi(theirAmt * 1000) - revokedLog := channeldb.RevocationLog{ - CommitTxHash: commitHash, - OurOutputIndex: uint16(localIndex), - TheirOutputIndex: uint16(remoteIndex), - HTLCEntries: []*channeldb.HTLCEntry{htlc}, - TheirBalance: &theirAmtMsat, - OurBalance: &ourAmtMsat, - } + revokedLog := channeldb.NewRevocationLog( + uint16(localIndex), uint16(remoteIndex), commitHash, + fn.Some(ourAmtMsat), fn.Some(theirAmtMsat), + []*channeldb.HTLCEntry{htlc}, fn.None[tlv.Blob](), + ) // Create a log with an empty local output index. revokedLogNoLocal := revokedLog - revokedLogNoLocal.OurOutputIndex = channeldb.OutputIndexEmpty + revokedLogNoLocal.OurOutputIndex.Val = channeldb.OutputIndexEmpty // Create a log with an empty remote output index. revokedLogNoRemote := revokedLog - revokedLogNoRemote.TheirOutputIndex = channeldb.OutputIndexEmpty + revokedLogNoRemote.TheirOutputIndex.Val = channeldb.OutputIndexEmpty testCases := []struct { name string @@ -10146,14 +10338,20 @@ func TestCreateBreachRetribution(t *testing.T) { { name: "fail due to our index too big", revocationLog: &channeldb.RevocationLog{ - OurOutputIndex: uint16(htlcIndex + 1), + //nolint:lll + OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( + uint16(htlcIndex + 1), + ), }, expectedErr: ErrOutputIndexOutOfRange, }, { name: "fail due to their index too big", revocationLog: &channeldb.RevocationLog{ - TheirOutputIndex: uint16(htlcIndex + 1), + //nolint:lll + TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1]( + uint16(htlcIndex + 1), + ), }, expectedErr: ErrOutputIndexOutOfRange, }, @@ -10222,11 +10420,12 @@ func TestCreateBreachRetribution(t *testing.T) { require.Equal(t, remote, br.RemoteOutpoint) for _, hr := range br.HtlcRetributions { - require.EqualValues(t, testAmt, - hr.SignDesc.Output.Value) + require.EqualValues( + t, testAmt, hr.SignDesc.Output.Value, + ) require.Equal(t, commitHash, hr.OutPoint.Hash) require.EqualValues(t, htlcIndex, hr.OutPoint.Index) - require.Equal(t, htlc.Incoming, hr.IsIncoming) + require.Equal(t, htlc.Incoming.Val, hr.IsIncoming) } } @@ -10242,6 +10441,7 @@ func TestCreateBreachRetribution(t *testing.T) { tc.revocationLog, tx, aliceChannel.channelState, keyRing, dummyPrivate, leaseExpiry, + fn.None[CommitAuxLeaves](), ) // Check the error if expected. @@ -10360,6 +10560,8 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // error as there are no past delta state saved as revocation logs yet. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10367,6 +10569,8 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // provided. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) @@ -10412,6 +10616,8 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // successfully. br, err := NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err) @@ -10423,6 +10629,8 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // since the necessary info should now be found in the revocation log. br, err = NewBreachRetribution( aliceChannel.channelState, stateNum, breachHeight, nil, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.NoError(t, err) assertRetribution(br, 1, 0) @@ -10431,6 +10639,8 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // error. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, breachTx, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) @@ -10438,12 +10648,14 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { // provided. _, err = NewBreachRetribution( aliceChannel.channelState, stateNum+1, breachHeight, nil, + fn.Some[AuxLeafStore](&MockAuxLeafStore{}), + fn.Some[AuxContractResolver](&MockAuxContractResolver{}), ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) } // TestExtractPayDescs asserts that `extractPayDescs` can correctly turn a -// slice of htlcs into two slices of PaymentDescriptors. +// slice of htlcs into two slices of paymentDescriptors. func TestExtractPayDescs(t *testing.T) { t.Parallel() @@ -10475,28 +10687,29 @@ func TestExtractPayDescs(t *testing.T) { // NOTE: we use nil commitment key rings to avoid checking the htlc // scripts(`genHtlcScript`) as it should be tested independently. incomingPDs, outgoingPDs, err := lnChan.extractPayDescs( - 0, htlcs, nil, nil, lntypes.Local, + 0, htlcs, lntypes.Dual[*CommitmentKeyRing]{}, lntypes.Local, + fn.None[CommitAuxLeaves](), ) require.NoError(t, err) - // Assert the incoming PaymentDescriptors are matched. + // Assert the incoming paymentDescriptors are matched. for i, pd := range incomingPDs { htlc := incomings[i] assertPayDescMatchHTLC(t, pd, htlc) } - // Assert the outgoing PaymentDescriptors are matched. + // Assert the outgoing paymentDescriptors are matched. for i, pd := range outgoingPDs { htlc := outgoings[i] assertPayDescMatchHTLC(t, pd, htlc) } } -// assertPayDescMatchHTLC compares a PaymentDescriptor to a channeldb.HTLC and +// assertPayDescMatchHTLC compares a paymentDescriptor to a channeldb.HTLC and // asserts that the fields are matched. -func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor, - htlc channeldb.HTLC, -) { +func assertPayDescMatchHTLC(t *testing.T, pd paymentDescriptor, + htlc channeldb.HTLC) { + require := require.New(t) require.EqualValues(htlc.RHash, pd.RHash, "RHash") @@ -10511,19 +10724,26 @@ func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor, // the `Incoming`. func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC { var onionBlob [lnwire.OnionPacketSize]byte - _, err := rand.Read(onionBlob[:]) + _, err := crand.Read(onionBlob[:]) require.NoError(t, err) var rHash [lntypes.HashSize]byte - _, err = rand.Read(rHash[:]) + _, err = crand.Read(rHash[:]) require.NoError(t, err) sig := make([]byte, 64) - _, err = rand.Read(sig) + _, err = crand.Read(sig) + require.NoError(t, err) + + randCustomData := make([]byte, 32) + _, err = crand.Read(randCustomData) require.NoError(t, err) + randCustomType := rand.Intn(255) + lnwire.MinCustomRecordsTlvType + blinding, err := pubkeyFromHex( - "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48236c39", //nolint:lll + "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48" + + "236c39", ) require.NoError(t, err) @@ -10538,9 +10758,13 @@ func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC { HtlcIndex: rand.Uint64(), LogIndex: rand.Uint64(), BlindingPoint: tlv.SomeRecordT( - //nolint:lll - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](blinding), + tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( + blinding, + ), ), + CustomRecords: map[uint64][]byte{ + uint64(randCustomType): randCustomData, + }, } } @@ -10737,7 +10961,7 @@ func TestAsynchronousSendingContraint(t *testing.T) { // Bob signs the new state for alice, which ONLY has his htlc on it // because he only includes acked updates of alice. // <----sig-------|--------------- - bobNewCommit, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) @@ -10753,7 +10977,7 @@ func TestAsynchronousSendingContraint(t *testing.T) { // incoming htlc in her commitment sig to bob, but this will dip her // local balance below her reserve because she already used everything // up when adding her htlc. - _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment(ctxb) require.ErrorIs(t, err, ErrBelowChanReserve) } @@ -10878,14 +11102,14 @@ func TestAsynchronousSendingWithFeeBuffer(t *testing.T) { // Force a state transition, this will lock-in the htlc of bob. // ------sig-----> (includes bob's htlc) // <----rev------ (locks in bob's htlc for alice) - aliceNewCommit, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err) err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) // Before testing the behavior of the fee buffer, we are going to fail @@ -10936,11 +11160,7 @@ func TestAsynchronousSendingWithFeeBuffer(t *testing.T) { aliceChannel.channelState.LocalChanCfg.DustLimit + htlcFee, ) htlc3, _ := createHTLC(1, htlcAmt3) - _, err = bobChannel.AddHTLC(htlc3, nil) - require.NoError(t, err) - - _, err = aliceChannel.ReceiveHTLC(htlc3) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc3, nil) err = ForceStateTransition(bobChannel, aliceChannel) require.NoError(t, err) @@ -11046,10 +11266,7 @@ func TestEnforceFeeBuffer(t *testing.T) { // --------------- |-----rev------> htlc1, _ := createHTLC(0, lnwire.NewMSatFromSatoshis(htlcAmt1)) - _, err = aliceChannel.AddHTLC(htlc1, nil) - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc1) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc1, nil) err = ForceStateTransition(aliceChannel, bobChannel) require.NoError(t, err) @@ -11063,10 +11280,7 @@ func TestEnforceFeeBuffer(t *testing.T) { htlcAmt2 := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcent) htlc2, _ := createHTLC(0, htlcAmt2) - _, err = bobChannel.AddHTLC(htlc2, nil) - require.NoError(t, err) - _, err = aliceChannel.ReceiveHTLC(htlc2) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc2, nil) err = ForceStateTransition(bobChannel, aliceChannel) require.NoError(t, err) @@ -11086,10 +11300,7 @@ func TestEnforceFeeBuffer(t *testing.T) { // <----rev------- |--------------- htlc4, _ := createHTLC(1, htlcAmt2) - _, err = bobChannel.AddHTLC(htlc4, nil) - require.NoError(t, err) - _, err = aliceChannel.ReceiveHTLC(htlc4) - require.NoError(t, err) + addAndReceiveHTLC(t, bobChannel, aliceChannel, htlc4, nil) err = ForceStateTransition(bobChannel, aliceChannel) require.NoError(t, err) @@ -11129,15 +11340,11 @@ func TestBlindingPointPersistence(t *testing.T) { tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](blinding), ) - _, err = aliceChannel.AddHTLC(htlc, nil) - - require.NoError(t, err) - _, err = bobChannel.ReceiveHTLC(htlc) - require.NoError(t, err) + addAndReceiveHTLC(t, aliceChannel, bobChannel, htlc, nil) // Now, Alice will send a new commitment to Bob, which will persist our // pending HTLC to disk. - aliceCommit, err := aliceChannel.SignNextCommitment() + aliceCommit, err := aliceChannel.SignNextCommitment(ctxb) require.NoError(t, err, "unable to sign commitment") // Restart alice to force fetching state from disk. @@ -11145,7 +11352,7 @@ func TestBlindingPointPersistence(t *testing.T) { require.NoError(t, err, "unable to restart alice") // Assert that the blinding point is restored from disk. - remoteCommit := aliceChannel.remoteCommitChain.tip() + remoteCommit := aliceChannel.commitChains.Remote.tip() require.Len(t, remoteCommit.outgoingHTLCs, 1) require.Equal(t, blinding, remoteCommit.outgoingHTLCs[0].BlindingPoint.UnwrapOrFailV(t)) @@ -11162,8 +11369,321 @@ func TestBlindingPointPersistence(t *testing.T) { require.NoError(t, err, "unable to restart bob's channel") // Assert that Bob is able to recover the blinding point from disk. - bobCommit := bobChannel.localCommitChain.tip() + bobCommit := bobChannel.commitChains.Local.tip() require.Len(t, bobCommit.incomingHTLCs, 1) require.Equal(t, blinding, bobCommit.incomingHTLCs[0].BlindingPoint.UnwrapOrFailV(t)) } + +// TestCreateCooperativeCloseTx tests that the cooperative close transaction is +// properly created based on the standard and also optional parameters. +func TestCreateCooperativeCloseTx(t *testing.T) { + t.Parallel() + + fundingTxIn := &wire.TxIn{} + + localDust := btcutil.Amount(400) + remoteDust := btcutil.Amount(400) + + localScript := []byte{0} + localExtraScript := []byte{2} + + remoteScript := []byte{1} + remoteExtraScript := []byte{3} + + tests := []struct { + name string + + enableRBF bool + + localBalance btcutil.Amount + remoteBalance btcutil.Amount + + extraCloseOutputs []CloseOutput + + expectedTx *wire.MsgTx + }{ + { + name: "no dust, no extra outputs", + localBalance: 1_000, + remoteBalance: 1_000, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 1_000, + PkScript: localScript, + }, + { + Value: 1_000, + PkScript: remoteScript, + }, + }, + }, + }, + { + name: "local dust, no extra outputs", + localBalance: 100, + remoteBalance: 1_000, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 1_000, + PkScript: remoteScript, + }, + }, + }, + }, + { + name: "remote dust, no extra outputs", + localBalance: 1_000, + remoteBalance: 100, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 1_000, + PkScript: localScript, + }, + }, + }, + }, + { + name: "no dust, local extra output", + localBalance: 10_000, + remoteBalance: 10_000, + extraCloseOutputs: []CloseOutput{ + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: localExtraScript, + }, + IsLocal: true, + }, + }, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 10_000, + PkScript: remoteScript, + }, + { + Value: 9_000, + PkScript: localScript, + }, + { + Value: 1_000, + PkScript: localExtraScript, + }, + }, + }, + }, + { + name: "no dust, remote extra output", + localBalance: 10_000, + remoteBalance: 10_000, + extraCloseOutputs: []CloseOutput{ + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: remoteExtraScript, + }, + IsLocal: false, + }, + }, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 10_000, + PkScript: localScript, + }, + { + Value: 9_000, + PkScript: remoteScript, + }, + { + Value: 1_000, + PkScript: remoteExtraScript, + }, + }, + }, + }, + { + name: "no dust, local+remote extra output", + localBalance: 10_000, + remoteBalance: 10_000, + extraCloseOutputs: []CloseOutput{ + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: remoteExtraScript, + }, + IsLocal: false, + }, + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: localExtraScript, + }, + IsLocal: true, + }, + }, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 9_000, + PkScript: localScript, + }, + { + Value: 9_000, + PkScript: remoteScript, + }, + { + Value: 1_000, + PkScript: remoteExtraScript, + }, + { + Value: 1_000, + PkScript: localExtraScript, + }, + }, + }, + }, + { + name: "no dust, local+remote extra output, " + + "remote can't afford", + localBalance: 10_000, + remoteBalance: 1_000, + extraCloseOutputs: []CloseOutput{ + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: remoteExtraScript, + }, + IsLocal: false, + }, + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: localExtraScript, + }, + IsLocal: true, + }, + }, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 9_000, + PkScript: localScript, + }, + { + Value: 1_000, + PkScript: remoteExtraScript, + }, + { + Value: 1_000, + PkScript: localExtraScript, + }, + }, + }, + }, + { + name: "no dust, local+remote extra output, " + + "local can't afford", + localBalance: 1_000, + remoteBalance: 10_000, + extraCloseOutputs: []CloseOutput{ + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: remoteExtraScript, + }, + IsLocal: false, + }, + { + TxOut: wire.TxOut{ + Value: 1_000, + PkScript: localExtraScript, + }, + IsLocal: true, + }, + }, + expectedTx: &wire.MsgTx{ + TxIn: []*wire.TxIn{ + fundingTxIn, + }, + Version: 2, + TxOut: []*wire.TxOut{ + { + Value: 9_000, + PkScript: remoteScript, + }, + { + Value: 1_000, + PkScript: remoteExtraScript, + }, + { + Value: 1_000, + PkScript: localExtraScript, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var opts []CloseTxOpt + if test.extraCloseOutputs != nil { + opts = append( + opts, + WithExtraTxCloseOutputs( + test.extraCloseOutputs, + ), + ) + } + + closeTx, err := CreateCooperativeCloseTx( + *fundingTxIn, localDust, remoteDust, + test.localBalance, test.remoteBalance, + localScript, remoteScript, opts..., + ) + require.NoError(t, err) + + txsort.InPlaceSort(test.expectedTx) + + require.Equal( + t, test.expectedTx, closeTx, + "expected %v, got %v", + spew.Sdump(test.expectedTx), + spew.Sdump(closeTx), + ) + }) + } +} diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 2cf58f494e..3f47819ead 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -11,14 +11,15 @@ 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/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" ) -// anchorSize is the constant anchor output size. -const anchorSize = btcutil.Amount(330) +// AnchorSize is the constant anchor output size. +const AnchorSize = btcutil.Amount(330) // DefaultAnchorsCommitMaxFeeRateSatPerVByte is the default max fee rate in // sat/vbyte the initiator will use for anchor channels. This should be enough @@ -200,9 +201,9 @@ func (w *WitnessScriptDesc) PkScript() []byte { return w.OutputScript } -// WitnessScript returns the witness script that we'll use when signing for the -// remote party, and also verifying signatures on our transactions. As an -// example, when we create an outgoing HTLC for the remote party, we want to +// WitnessScriptToSign returns the witness script that we'll use when signing +// for the remote party, and also verifying signatures on our transactions. As +// an example, when we create an outgoing HTLC for the remote party, we want to // sign their success path. func (w *WitnessScriptDesc) WitnessScriptToSign() []byte { return w.WitnessScript @@ -210,10 +211,10 @@ func (w *WitnessScriptDesc) WitnessScriptToSign() []byte { // WitnessScriptForPath returns the witness script for the given spending path. // An error is returned if the path is unknown. This is useful as when -// constructing a contrl block for a given path, one also needs witness script +// constructing a control block for a given path, one also needs witness script // being signed. -func (w *WitnessScriptDesc) WitnessScriptForPath(_ input.ScriptPath, -) ([]byte, error) { +func (w *WitnessScriptDesc) WitnessScriptForPath( + _ input.ScriptPath) ([]byte, error) { return w.WitnessScript, nil } @@ -226,8 +227,7 @@ func (w *WitnessScriptDesc) WitnessScriptForPath(_ input.ScriptPath, // the settled funds in the channel, plus the unsettled funds. func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32, -) ( - input.ScriptDescriptor, error) { + auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) { switch { // For taproot scripts, we'll need to make a slightly modified script @@ -237,7 +237,7 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // Our "redeem" script here is just the taproot witness program. case chanType.IsTaproot(): return input.NewLocalCommitScriptTree( - csvDelay, selfKey, revokeKey, + csvDelay, selfKey, revokeKey, auxLeaf, ) // If we are the initiator of a leased channel, then we have an @@ -291,8 +291,8 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // script for. The second return value is the CSV delay of the output script, // what must be satisfied in order to spend the output. func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, - remoteKey *btcec.PublicKey, - leaseExpiry uint32) (input.ScriptDescriptor, uint32, error) { + remoteKey *btcec.PublicKey, leaseExpiry uint32, + auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, uint32, error) { switch { // If we are not the initiator of a leased channel, then the remote @@ -321,7 +321,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // with the sole tap leaf enforcing the 1 CSV delay. case chanType.IsTaproot(): toRemoteScriptTree, err := input.NewRemoteCommitScriptTree( - remoteKey, + remoteKey, auxLeaf, ) if err != nil { return nil, 0, err @@ -420,14 +420,14 @@ func sweepSigHash(chanType channeldb.ChannelType) txscript.SigHashType { // argument should correspond to the owner of the commitment transaction which // we are generating the to_local script for. func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool, - revocationKey, delayKey *btcec.PublicKey, - csvDelay, leaseExpiry uint32) (input.ScriptDescriptor, error) { + revocationKey, delayKey *btcec.PublicKey, csvDelay, leaseExpiry uint32, + auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) { switch { // For taproot channels, the pkScript is a segwit v1 p2tr output. case chanType.IsTaproot(): return input.TaprootSecondLevelScriptTree( - revocationKey, delayKey, csvDelay, + revocationKey, delayKey, csvDelay, auxLeaf, ) // If we are the initiator of a leased channel, then we have an @@ -532,8 +532,8 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, input.ScriptDescriptor, input.ScriptDescriptor, error) { var ( - anchorScript func(key *btcec.PublicKey) ( - input.ScriptDescriptor, error) + anchorScript func( + key *btcec.PublicKey) (input.ScriptDescriptor, error) keySelector func(*channeldb.ChannelConfig, bool) *btcec.PublicKey @@ -544,12 +544,10 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, // level key is now the (relative) local delay and remote public key, // since these are fully revealed once the commitment hits the chain. case chanType.IsTaproot(): - anchorScript = func(key *btcec.PublicKey, - ) (input.ScriptDescriptor, error) { + anchorScript = func( + key *btcec.PublicKey) (input.ScriptDescriptor, error) { - return input.NewAnchorScriptTree( - key, - ) + return input.NewAnchorScriptTree(key) } keySelector = func(cfg *channeldb.ChannelConfig, @@ -567,8 +565,8 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, default: // For normal channels, we'll create a p2wsh script based on // the target key. - anchorScript = func(key *btcec.PublicKey, - ) (input.ScriptDescriptor, error) { + anchorScript = func( + key *btcec.PublicKey) (input.ScriptDescriptor, error) { script, err := input.CommitScriptAnchor(key) if err != nil { @@ -615,7 +613,7 @@ func CommitScriptAnchors(chanType channeldb.ChannelType, // with, and abstracts the various ways of constructing commitment // transactions. type CommitmentBuilder struct { - // chanState is the underlying channels's state struct, used to + // chanState is the underlying channel's state struct, used to // determine the type of channel we are dealing with, and relevant // parameters. chanState *channeldb.OpenChannel @@ -623,18 +621,25 @@ type CommitmentBuilder struct { // obfuscator is a 48-bit state hint that's used to obfuscate the // current state number on the commitment transactions. obfuscator [StateHintSize]byte + + // auxLeafStore is an interface that allows us to fetch auxiliary + // tapscript leaves for the commitment output. + auxLeafStore fn.Option[AuxLeafStore] } // NewCommitmentBuilder creates a new CommitmentBuilder from chanState. -func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder { +func NewCommitmentBuilder(chanState *channeldb.OpenChannel, + leafStore fn.Option[AuxLeafStore]) *CommitmentBuilder { + // The anchor channel type MUST be tweakless. if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() { panic("invalid channel type combination") } return &CommitmentBuilder{ - chanState: chanState, - obfuscator: createStateHintObfuscator(chanState), + chanState: chanState, + obfuscator: createStateHintObfuscator(chanState), + auxLeafStore: leafStore, } } @@ -687,9 +692,9 @@ type unsignedCommitmentTx struct { // fees, but after anchor outputs. func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, theirBalance lnwire.MilliSatoshi, whoseCommit lntypes.ChannelParty, - feePerKw chainfee.SatPerKWeight, height uint64, - filteredHTLCView *htlcView, - keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) { + feePerKw chainfee.SatPerKWeight, height uint64, originalHtlcView, + filteredHTLCView *HtlcView, keyRing *CommitmentKeyRing, + prevCommit *commitment) (*unsignedCommitmentTx, error) { dustLimit := cb.chanState.LocalChanCfg.DustLimit if whoseCommit.IsRemote() { @@ -697,7 +702,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, } numHTLCs := int64(0) - for _, htlc := range filteredHTLCView.ourUpdates { + for _, htlc := range filteredHTLCView.OurUpdates { if HtlcIsDust( cb.chanState.ChanType, false, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -708,7 +713,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, numHTLCs++ } - for _, htlc := range filteredHTLCView.theirUpdates { + for _, htlc := range filteredHTLCView.TheirUpdates { if HtlcIsDust( cb.chanState.ChanType, true, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -750,10 +755,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, theirBalance -= commitFeeMSat } - var ( - commitTx *wire.MsgTx - err error - ) + var commitTx *wire.MsgTx + + // Before we create the commitment transaction below, we'll try to see + // if there're any aux leaves that need to be a part of the tapscript + // tree. We'll only do this if we have a custom blob defined though. + auxResult, err := fn.MapOptionZ( + cb.auxLeafStore, + func(s AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return auxLeavesFromView( + s, cb.chanState, prevCommit.customBlob, + originalHtlcView, whoseCommit, ourBalance, + theirBalance, *keyRing, + ) + }, + ).Unpack() + if err != nil { + return nil, fmt.Errorf("unable to fetch aux leaves: %w", err) + } // Depending on whether the transaction is ours or not, we call // CreateCommitTx with parameters matching the perspective, to generate @@ -769,6 +788,7 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), numHTLCs, cb.chanState.IsInitiator, leaseExpiry, + auxResult.AuxLeaves, ) } else { commitTx, err = CreateCommitTx( @@ -776,12 +796,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), numHTLCs, !cb.chanState.IsInitiator, leaseExpiry, + auxResult.AuxLeaves, ) } if err != nil { return nil, err } + // Similarly, we'll now attempt to extract the set of aux leaves for + // the set of incoming and outgoing HTLCs. + incomingAuxLeaves := fn.MapOption( + func(leaves CommitAuxLeaves) input.HtlcAuxLeaves { + return leaves.IncomingHtlcLeaves + }, + )(auxResult.AuxLeaves) + outgoingAuxLeaves := fn.MapOption( + func(leaves CommitAuxLeaves) input.HtlcAuxLeaves { + return leaves.OutgoingHtlcLeaves + }, + )(auxResult.AuxLeaves) + // We'll now add all the HTLC outputs to the commitment transaction. // Each output includes an off-chain 2-of-2 covenant clause, so we'll // need the objective local/remote keys for this particular commitment @@ -792,7 +826,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // commitment outputs and should correspond to zero values for the // purposes of sorting. cltvs := make([]uint32, len(commitTx.TxOut)) - for _, htlc := range filteredHTLCView.ourUpdates { + htlcIndexes := make([]input.HtlcIndex, len(commitTx.TxOut)) + for _, htlc := range filteredHTLCView.OurUpdates { if HtlcIsDust( cb.chanState.ChanType, false, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -801,16 +836,26 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, continue } + auxLeaf := fn.ChainOption( + func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf { + return leaves[htlc.HtlcIndex].AuxTapLeaf + }, + )(outgoingAuxLeaves) + err := addHTLC( commitTx, whoseCommit, false, htlc, keyRing, - cb.chanState.ChanType, + cb.chanState.ChanType, auxLeaf, ) if err != nil { return nil, err } - cltvs = append(cltvs, htlc.Timeout) // nolint:makezero + + // We want to add the CLTV and HTLC index to their respective + // slices, even if we already pre-allocated them. + cltvs = append(cltvs, htlc.Timeout) //nolint + htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint } - for _, htlc := range filteredHTLCView.theirUpdates { + for _, htlc := range filteredHTLCView.TheirUpdates { if HtlcIsDust( cb.chanState.ChanType, true, whoseCommit, feePerKw, htlc.Amount.ToSatoshis(), dustLimit, @@ -819,14 +864,24 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, continue } + auxLeaf := fn.ChainOption( + func(leaves input.HtlcAuxLeaves) input.AuxTapLeaf { + return leaves[htlc.HtlcIndex].AuxTapLeaf + }, + )(incomingAuxLeaves) + err := addHTLC( commitTx, whoseCommit, true, htlc, keyRing, - cb.chanState.ChanType, + cb.chanState.ChanType, auxLeaf, ) if err != nil { return nil, err } - cltvs = append(cltvs, htlc.Timeout) // nolint:makezero + + // We want to add the CLTV and HTLC index to their respective + // slices, even if we already pre-allocated them. + cltvs = append(cltvs, htlc.Timeout) //nolint + htlcIndexes = append(htlcIndexes, htlc.HtlcIndex) //nolint } // Set the state hint of the commitment transaction to facilitate @@ -838,9 +893,16 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, } // Sort the transactions according to the agreed upon canonical - // ordering. This lets us skip sending the entire transaction over, - // instead we'll just send signatures. - InPlaceCommitSort(commitTx, cltvs) + // ordering (which might be customized for custom channel types, but + // deterministic and both parties will arrive at the same result). This + // lets us skip sending the entire transaction over, instead we'll just + // send signatures. + commitSort := auxResult.CommitSortFunc.UnwrapOr(DefaultCommitSort) + err = commitSort(commitTx, cltvs, htlcIndexes) + if err != nil { + return nil, fmt.Errorf("unable to sort commitment "+ + "transaction: %w", err) + } // Next, we'll ensure that we don't accidentally create a commitment // transaction which would be invalid by consensus. @@ -882,24 +944,33 @@ func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, amountToLocal, amountToRemote btcutil.Amount, - numHTLCs int64, initiator bool, leaseExpiry uint32) (*wire.MsgTx, error) { + numHTLCs int64, initiator bool, leaseExpiry uint32, + auxLeaves fn.Option[CommitAuxLeaves]) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. // This output has 2 main redemption clauses: either we can redeem the // output after a relative block delay, or the remote node can claim // the funds with the revocation key if we broadcast a revoked // commitment transaction. + localAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.LocalAuxLeaf + })(auxLeaves) toLocalScript, err := CommitScriptToSelf( chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey, uint32(localChanCfg.CsvDelay), leaseExpiry, + fn.FlattenOption(localAuxLeaf), ) if err != nil { return nil, err } // Next, we create the script paying to the remote. + remoteAuxLeaf := fn.MapOption(func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.RemoteAuxLeaf + })(auxLeaves) toRemoteScript, _, err := CommitScriptToRemote( chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, + fn.FlattenOption(remoteAuxLeaf), ) if err != nil { return nil, err @@ -942,7 +1013,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, if localOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ PkScript: localAnchor.PkScript(), - Value: int64(anchorSize), + Value: int64(AnchorSize), }) } @@ -951,7 +1022,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, if remoteOutput || numHTLCs > 0 { commitTx.AddTxOut(&wire.TxOut{ PkScript: remoteAnchor.PkScript(), - Value: int64(anchorSize), + Value: int64(AnchorSize), }) } } @@ -962,21 +1033,17 @@ func CreateCommitTx(chanType channeldb.ChannelType, // CoopCloseBalance returns the final balances that should be used to create // the cooperative close tx, given the channel type and transaction fee. func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, - coopCloseFee btcutil.Amount, localCommit channeldb.ChannelCommitment) ( - btcutil.Amount, btcutil.Amount, error) { - - // Get both parties' balances from the latest commitment. - ourBalance := localCommit.LocalBalance.ToSatoshis() - theirBalance := localCommit.RemoteBalance.ToSatoshis() + coopCloseFee, ourBalance, theirBalance, + commitFee btcutil.Amount) (btcutil.Amount, btcutil.Amount, error) { // We'll make sure we account for the complete balance by adding the // current dangling commitment fee to the balance of the initiator. - initiatorDelta := localCommit.CommitFee + initiatorDelta := commitFee // Since the initiator's balance also is stored after subtracting the // anchor values, add that back in case this was an anchor commitment. if chanType.HasAnchors() { - initiatorDelta += 2 * anchorSize + initiatorDelta += 2 * AnchorSize } // The initiator will pay the full coop close fee, subtract that value @@ -1075,11 +1142,11 @@ func genSegwitV0HtlcScript(chanType channeldb.ChannelType, }, nil } -// genTaprootHtlcScript generates the HTLC scripts for a taproot+musig2 +// GenTaprootHtlcScript generates the HTLC scripts for a taproot+musig2 // channel. -func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, +func GenTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, -) (*input.HtlcScriptTree, error) { + auxLeaf input.AuxTapLeaf) (*input.HtlcScriptTree, error) { var ( htlcScriptTree *input.HtlcScriptTree @@ -1096,7 +1163,7 @@ func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, case isIncoming && whoseCommit.IsLocal(): htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:], whoseCommit, + keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, ) // We're being paid via an HTLC by the remote party, and the HTLC is @@ -1105,7 +1172,7 @@ func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, case isIncoming && whoseCommit.IsRemote(): htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:], whoseCommit, + keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, ) // We're sending an HTLC which is being added to our commitment @@ -1114,7 +1181,7 @@ func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, case !isIncoming && whoseCommit.IsLocal(): htlcScriptTree, err = input.SenderHTLCScriptTaproot( keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, - keyRing.RevocationKey, rHash[:], whoseCommit, + keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, ) // Finally, we're paying the remote party via an HTLC, which is being @@ -1123,7 +1190,7 @@ func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, case !isIncoming && whoseCommit.IsRemote(): htlcScriptTree, err = input.ReceiverHTLCScriptTaproot( timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, - keyRing.RevocationKey, rHash[:], whoseCommit, + keyRing.RevocationKey, rHash[:], whoseCommit, auxLeaf, ) } @@ -1139,7 +1206,7 @@ func genTaprootHtlcScript(isIncoming bool, whoseCommit lntypes.ChannelParty, func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool, whoseCommit lntypes.ChannelParty, timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing, -) (input.ScriptDescriptor, error) { + auxLeaf input.AuxTapLeaf) (input.ScriptDescriptor, error) { if !chanType.IsTaproot() { return genSegwitV0HtlcScript( @@ -1148,8 +1215,8 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool, ) } - return genTaprootHtlcScript( - isIncoming, whoseCommit, timeout, rHash, keyRing, + return GenTaprootHtlcScript( + isIncoming, whoseCommit, timeout, rHash, keyRing, auxLeaf, ) } @@ -1158,17 +1225,19 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming bool, // is incoming and if it's being applied to our commitment transaction or that // of the remote node's. Additionally, in order to be able to efficiently // locate the added HTLC on the commitment transaction from the -// PaymentDescriptor that generated it, the generated script is stored within +// paymentDescriptor that generated it, the generated script is stored within // the descriptor itself. func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty, - isIncoming bool, paymentDesc *PaymentDescriptor, - keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error { + isIncoming bool, paymentDesc *paymentDescriptor, + keyRing *CommitmentKeyRing, chanType channeldb.ChannelType, + auxLeaf input.AuxTapLeaf) error { timeout := paymentDesc.Timeout rHash := paymentDesc.RHash scriptInfo, err := genHtlcScript( chanType, isIncoming, whoseCommit, timeout, rHash, keyRing, + auxLeaf, ) if err != nil { return err @@ -1180,7 +1249,7 @@ func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty, amountPending := int64(paymentDesc.Amount.ToSatoshis()) commitTx.AddTxOut(wire.NewTxOut(amountPending, pkScript)) - // Store the pkScript of this particular PaymentDescriptor so we can + // Store the pkScript of this particular paymentDescriptor so we can // quickly locate it within the commitment transaction later. if whoseCommit.IsLocal() { paymentDesc.ourPkScript = pkScript @@ -1201,7 +1270,8 @@ func addHTLC(commitTx *wire.MsgTx, whoseCommit lntypes.ChannelParty, // output scripts and compares them against the outputs inside the commitment // to find the match. func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, - chanState *channeldb.OpenChannel) (uint32, uint32, error) { + chanState *channeldb.OpenChannel, + leafStore fn.Option[AuxLeafStore]) (uint32, uint32, error) { // Init the output indexes as empty. ourIndex := uint32(channeldb.OutputIndexEmpty) @@ -1231,26 +1301,51 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, leaseExpiry = chanState.ThawHeight } - // Map the scripts from our PoV. When facing a local commitment, the to - // local output belongs to us and the to remote output belongs to them. - // When facing a remote commitment, the to local output belongs to them - // and the to remote output belongs to us. + // If we have a custom blob, then we'll attempt to fetch the aux leaves + // for this state. + auxResult, err := fn.MapOptionZ( + leafStore, func(a AuxLeafStore) fn.Result[CommitDiffAuxResult] { + return a.FetchLeavesFromCommit( + NewAuxChanState(chanState), chanCommit, + *keyRing, lntypes.Remote, + ) + }, + ).Unpack() + if err != nil { + return ourIndex, theirIndex, fmt.Errorf("unable to fetch aux "+ + "leaves: %w", err) + } - // Compute the to local script. From our PoV, when facing a remote - // commitment, the to local output belongs to them. + // Map the scripts from our PoV. When facing a local commitment, the + // to_local output belongs to us and the to_remote output belongs to + // them. When facing a remote commitment, the to_local output belongs to + // them and the to_remote output belongs to us. + + // Compute the to_local script. From our PoV, when facing a remote + // commitment, the to_local output belongs to them. + localAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.LocalAuxLeaf + }, + )(auxResult.AuxLeaves) theirScript, err := CommitScriptToSelf( chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey, - keyRing.RevocationKey, theirDelay, leaseExpiry, + keyRing.RevocationKey, theirDelay, leaseExpiry, localAuxLeaf, ) if err != nil { return ourIndex, theirIndex, err } - // Compute the to remote script. From our PoV, when facing a remote - // commitment, the to remote output belongs to us. + // Compute the to_remote script. From our PoV, when facing a remote + // commitment, the to_remote output belongs to us. + remoteAuxLeaf := fn.ChainOption( + func(l CommitAuxLeaves) input.AuxTapLeaf { + return l.RemoteAuxLeaf + }, + )(auxResult.AuxLeaves) ourScript, _, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, remoteAuxLeaf, ) if err != nil { return ourIndex, theirIndex, err diff --git a/lnwallet/config.go b/lnwallet/config.go index 7eeacb6ea2..425fe15dad 100644 --- a/lnwallet/config.go +++ b/lnwallet/config.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -62,4 +63,12 @@ type Config struct { // CoinSelectionStrategy is the strategy that is used for selecting // coins when funding a transaction. CoinSelectionStrategy wallet.CoinSelectionStrategy + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[AuxLeafStore] + + // AuxSigner is an optional signer that can be used to sign auxiliary + // leaves for certain custom channel types. + AuxSigner fn.Option[AuxSigner] } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index af5955d8e6..9a8d9fa82e 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" @@ -21,6 +22,7 @@ import ( "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwire" ) const ( @@ -594,6 +596,81 @@ 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 { + // If the error is that the address can't be found, it is not + // an error. This happens when any channel which is not a custom + // taproot channel is cooperatively closed to an external P2TR + // address. In this case there is no internal key associated + // with the address. Callers can use the .Option() method to get + // an option value. + var managerErr waddrmgr.ManagerError + if errors.As(err, &managerErr) && + managerErr.ErrorCode == waddrmgr.ErrAddressNotFound { + + return none, 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 e6bd0b2e41..16f2d464bf 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -17,8 +17,12 @@ import ( "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/mock" ) var ( @@ -389,3 +393,122 @@ func (*mockChainIO) GetBlockHeader( return nil, nil } + +type MockAuxLeafStore struct{} + +// A compile time check to ensure that MockAuxLeafStore implements the +// AuxLeafStore interface. +var _ AuxLeafStore = (*MockAuxLeafStore)(nil) + +// FetchLeavesFromView attempts to fetch the auxiliary leaves that +// correspond to the passed aux blob, and pending original (unfiltered) +// HTLC view. +func (*MockAuxLeafStore) FetchLeavesFromView( + _ CommitDiffAuxInput) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that +// correspond to the passed aux blob, and an existing channel +// commitment. +func (*MockAuxLeafStore) FetchLeavesFromCommit(_ AuxChanState, + _ channeldb.ChannelCommitment, _ CommitmentKeyRing, + _ lntypes.ChannelParty) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// FetchLeavesFromRevocation attempts to fetch the auxiliary leaves +// from a channel revocation that stores balance + blob information. +func (*MockAuxLeafStore) FetchLeavesFromRevocation( + _ *channeldb.RevocationLog) fn.Result[CommitDiffAuxResult] { + + return fn.Ok(CommitDiffAuxResult{}) +} + +// ApplyHtlcView serves as the state transition function for the custom +// channel's blob. Given the old blob, and an HTLC view, then a new +// blob should be returned that reflects the pending updates. +func (*MockAuxLeafStore) ApplyHtlcView( + _ CommitDiffAuxInput) fn.Result[fn.Option[tlv.Blob]] { + + return fn.Ok(fn.None[tlv.Blob]()) +} + +// EmptyMockJobHandler is a mock job handler that just sends an empty response +// to all jobs. +func EmptyMockJobHandler(jobs []AuxSigJob) { + for _, sigJob := range jobs { + sigJob.Resp <- AuxSigJobResp{} + } +} + +// MockAuxSigner is a mock implementation of the AuxSigner interface. +type MockAuxSigner struct { + mock.Mock + + jobHandlerFunc func([]AuxSigJob) +} + +// NewAuxSignerMock creates a new mock aux signer with the given job handler. +func NewAuxSignerMock(jobHandler func([]AuxSigJob)) *MockAuxSigner { + return &MockAuxSigner{ + jobHandlerFunc: jobHandler, + } +} + +// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and +// processes them asynchronously. +func (a *MockAuxSigner) SubmitSecondLevelSigBatch(chanState AuxChanState, + tx *wire.MsgTx, jobs []AuxSigJob) error { + + args := a.Called(chanState, tx, jobs) + + if a.jobHandlerFunc != nil { + a.jobHandlerFunc(jobs) + } + + return args.Error(0) +} + +// PackSigs takes a series of aux signatures and packs them into a +// single blob that can be sent alongside the CommitSig messages. +func (a *MockAuxSigner) PackSigs( + sigs []fn.Option[tlv.Blob]) fn.Result[fn.Option[tlv.Blob]] { + + args := a.Called(sigs) + + return args.Get(0).(fn.Result[fn.Option[tlv.Blob]]) +} + +// UnpackSigs takes a packed blob of signatures and returns the +// original signatures for each HTLC, keyed by HTLC index. +func (a *MockAuxSigner) UnpackSigs( + sigs fn.Option[tlv.Blob]) fn.Result[[]fn.Option[tlv.Blob]] { + + args := a.Called(sigs) + + return args.Get(0).(fn.Result[[]fn.Option[tlv.Blob]]) +} + +// VerifySecondLevelSigs attempts to synchronously verify a batch of aux +// sig jobs. +func (a *MockAuxSigner) VerifySecondLevelSigs(chanState AuxChanState, + tx *wire.MsgTx, jobs []AuxVerifyJob) error { + + args := a.Called(chanState, tx, jobs) + + 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/musig_session.go b/lnwallet/musig_session.go index ecc60d07f1..c3214d3f2f 100644 --- a/lnwallet/musig_session.go +++ b/lnwallet/musig_session.go @@ -8,8 +8,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" @@ -37,6 +39,20 @@ var ( ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized") ) +// tapscriptRootToSignOpt is a function that takes a tapscript root and returns +// a MuSig2 sign opt that'll apply the tweak when signing+verifying. +func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption { + return musig2.WithTaprootSignTweak(root[:]) +} + +// TapscriptRootToTweak is a helper function that converts a tapscript root +// into a tweak that can be used with the MuSig2 API. +func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks { + return input.MuSig2Tweaks{ + TaprootTweak: root[:], + } +} + // MusigPartialSig is a wrapper around the base musig2.PartialSignature type // that also includes information about the set of nonces used, and also the // signer. This allows us to implement the input.Signature interface, as that @@ -54,25 +70,30 @@ type MusigPartialSig struct { // signerKeys is the set of public keys of all signers. signerKeys []*btcec.PublicKey + + // tapscriptTweak is an optional tweak, that if specified, will be used + // instead of the normal BIP 86 tweak when validating the signature. + tapscriptTweak fn.Option[chainhash.Hash] } -// NewMusigPartialSig creates a new musig partial signature. -func NewMusigPartialSig(sig *musig2.PartialSignature, - signerNonce, combinedNonce lnwire.Musig2Nonce, - signerKeys []*btcec.PublicKey) *MusigPartialSig { +// NewMusigPartialSig creates a new MuSig2 partial signature. +func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce, + combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey, + tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig { return &MusigPartialSig{ - sig: sig, - signerNonce: signerNonce, - combinedNonce: combinedNonce, - signerKeys: signerKeys, + sig: sig, + signerNonce: signerNonce, + combinedNonce: combinedNonce, + signerKeys: signerKeys, + tapscriptTweak: tapscriptTweak, } } // FromWireSig maps a wire partial sig to this internal type that we'll use to // perform signature validation. -func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce, -) *MusigPartialSig { +func (p *MusigPartialSig) FromWireSig( + sig *lnwire.PartialSigWithNonce) *MusigPartialSig { p.sig = &musig2.PartialSignature{ S: &sig.Sig, @@ -135,9 +156,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { var m [32]byte copy(m[:], msg) + // If we have a tapscript tweak, then we'll use that as a tweak + // otherwise, we'll fall back to the normal BIP 86 sign tweak. + signOpts := fn.MapOption(tapscriptRootToSignOpt)( + p.tapscriptTweak, + ).UnwrapOr(musig2.WithBip86SignTweak()) + return p.sig.Verify( p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, - musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), + musig2.WithSortedKeys(), signOpts, ) } @@ -160,6 +187,14 @@ func (n *MusigNoncePair) String() string { n.SigningNonce.PubNonce[:]) } +// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and +// returns the root hash of the tapscript tree. +func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash { + var root chainhash.Hash + copy(root[:], tweak.TaprootTweak) + return root +} + // MusigSession abstracts over the details of a logical musig session. A single // session is used for each commitment transactions. The sessions use a JIT // nonce style, wherein part of the session can be created using only the @@ -197,15 +232,20 @@ type MusigSession struct { // commitType tracks if this is the session for the local or remote // commitment. commitType MusigCommitType + + // tapscriptTweak is an optional tweak, that if specified, will be used + // instead of the normal BIP 86 tweak when creating the MuSig2 + // aggregate key and session. + tapscriptTweak fn.Option[input.MuSig2Tweaks] } // NewPartialMusigSession creates a new musig2 session given only the // verification nonce (local nonce), and the other information that has already // been bound to the session. func NewPartialMusigSession(verificationNonce musig2.Nonces, - localKey, remoteKey keychain.KeyDescriptor, - signer input.MuSig2Signer, inputTxOut *wire.TxOut, - commitType MusigCommitType) *MusigSession { + localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer, + inputTxOut *wire.TxOut, commitType MusigCommitType, + tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession { signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} @@ -214,13 +254,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces, } return &MusigSession{ - nonces: nonces, - remoteKey: remoteKey, - localKey: localKey, - inputTxOut: inputTxOut, - signerKeys: signerKeys, - signer: signer, - commitType: commitType, + nonces: nonces, + remoteKey: remoteKey, + localKey: localKey, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + commitType: commitType, + tapscriptTweak: tapscriptTweak, } } @@ -254,9 +295,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error { remoteNonce = m.nonces.SigningNonce } - tweakDesc := input.MuSig2Tweaks{ + tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{ TaprootBIP0086Tweak: true, - } + }) m.session, err = m.signer.MuSig2CreateSession( input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys, &tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, @@ -351,8 +392,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) { return nil, err } + tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak) + return NewMusigPartialSig( sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, + tapscriptRoot, ), nil } @@ -364,7 +408,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces, return NewPartialMusigSession( *verificationNonce, m.localKey, m.remoteKey, m.signer, - m.inputTxOut, m.commitType, + m.inputTxOut, m.commitType, m.tapscriptTweak, ), nil } @@ -451,9 +495,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, // When we verify a commitment signature, we always assume that we're // verifying a signature on our local commitment. Therefore, we'll use: // their remote nonce, and also public key. + tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak) partialSig := NewMusigPartialSig( &musig2.PartialSignature{S: &sig.Sig}, m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys, + tapscriptRoot, ) // With the partial sig loaded with the proper context, we'll now @@ -537,6 +583,10 @@ type MusigSessionCfg struct { // InputTxOut is the output that we're signing for. This will be the // funding input. InputTxOut *wire.TxOut + + // TapscriptRoot is an optional tweak that can be used to modify the + // MuSig2 public key used in the session. + TapscriptTweak fn.Option[chainhash.Hash] } // MusigPairSession houses the two musig2 sessions needed to do funding and @@ -561,13 +611,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession { // // Both sessions will be created using only the verification nonce for // the local+remote party. + tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak) localSession := NewPartialMusigSession( - cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, - cfg.Signer, cfg.InputTxOut, LocalMusigCommit, + cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, + cfg.InputTxOut, LocalMusigCommit, tapscriptTweak, ) remoteSession := NewPartialMusigSession( - cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, - cfg.Signer, cfg.InputTxOut, RemoteMusigCommit, + cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer, + cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak, ) return &MusigPairSession{ diff --git a/lnwallet/payment_descriptor.go b/lnwallet/payment_descriptor.go index f0ac8b9f7e..5a51f29ce5 100644 --- a/lnwallet/payment_descriptor.go +++ b/lnwallet/payment_descriptor.go @@ -62,14 +62,19 @@ func (u updateType) String() string { } } -// PaymentDescriptor represents a commitment state update which either adds, -// settles, or removes an HTLC. PaymentDescriptors encapsulate all necessary +// paymentDescriptor represents a commitment state update which either adds, +// settles, or removes an HTLC. paymentDescriptors encapsulate all necessary // metadata w.r.t to an HTLC, and additional data pairing a settle message to // the original added HTLC. // // TODO(roasbeef): LogEntry interface?? // - need to separate attrs for cancel/add/settle/feeupdate -type PaymentDescriptor struct { +type paymentDescriptor struct { + // ChanID is the ChannelID of the LightningChannel that this + // paymentDescriptor belongs to. We track this here so we can + // reconstruct the Messages that this paymentDescriptor is built from. + ChanID lnwire.ChannelID + // RHash is the payment hash for this HTLC. The HTLC can be settled iff // the preimage to this hash is presented. RHash PaymentHash @@ -165,7 +170,7 @@ type PaymentDescriptor struct { // removeCommitHeight[Remote|Local] encodes the height of the // commitment which removed the parent pointer of this - // PaymentDescriptor either due to a timeout or a settle. Once both + // paymentDescriptor either due to a timeout or a settle. Once both // these heights are below the tail of both chains, the log entries can // safely be removed. removeCommitHeightRemote uint64 @@ -175,7 +180,7 @@ type PaymentDescriptor struct { // routing. // // NOTE: Populated only on add payment descriptor entry types. - OnionBlob []byte + OnionBlob [lnwire.OnionPacketSize]byte // ShaOnionBlob is a sha of the onion blob. // @@ -194,7 +199,7 @@ type PaymentDescriptor struct { // [our|their|]PkScript are the raw public key scripts that encodes the // redemption rules for this particular HTLC. These fields will only be - // populated iff the EntryType of this PaymentDescriptor is Add. + // populated iff the EntryType of this paymentDescriptor is Add. // ourPkScript is the ourPkScript from the context of our local // commitment chain. theirPkScript is the latest pkScript from the // context of the remote commitment chain. @@ -207,7 +212,7 @@ type PaymentDescriptor struct { theirPkScript []byte theirWitnessScript []byte - // EntryType denotes the exact type of the PaymentDescriptor. In the + // EntryType denotes the exact type of the paymentDescriptor. In the // case of a Timeout, or Settle type, then the Parent field will point // into the log to the HTLC being modified. EntryType updateType @@ -221,4 +226,61 @@ type PaymentDescriptor struct { // blinded route (ie, not the introduction node) from update_add_htlc's // TLVs. BlindingPoint lnwire.BlindingPointRecord + + // CustomRecords also stores the set of optional custom records that + // may have been attached to a sent HTLC. + CustomRecords lnwire.CustomRecords +} + +// toLogUpdate recovers the underlying LogUpdate from the paymentDescriptor. +// This operation is lossy and will forget some extra information tracked by the +// paymentDescriptor but the function is total in that all paymentDescriptors +// can be converted back to LogUpdates. +func (pd *paymentDescriptor) toLogUpdate() channeldb.LogUpdate { + var msg lnwire.Message + switch pd.EntryType { + case Add: + msg = &lnwire.UpdateAddHTLC{ + ChanID: pd.ChanID, + ID: pd.HtlcIndex, + Amount: pd.Amount, + PaymentHash: pd.RHash, + Expiry: pd.Timeout, + OnionBlob: pd.OnionBlob, + BlindingPoint: pd.BlindingPoint, + CustomRecords: pd.CustomRecords.Copy(), + } + case Settle: + msg = &lnwire.UpdateFulfillHTLC{ + ChanID: pd.ChanID, + ID: pd.ParentIndex, + PaymentPreimage: pd.RPreimage, + } + case Fail: + msg = &lnwire.UpdateFailHTLC{ + ChanID: pd.ChanID, + ID: pd.ParentIndex, + Reason: pd.FailReason, + } + case MalformedFail: + msg = &lnwire.UpdateFailMalformedHTLC{ + ChanID: pd.ChanID, + ID: pd.ParentIndex, + ShaOnionBlob: pd.ShaOnionBlob, + FailureCode: pd.FailCode, + } + case FeeUpdate: + // The Amount field holds the feerate denominated in + // msat. Since feerates are only denominated in sat/kw, + // we can convert it without loss of precision. + msg = &lnwire.UpdateFee{ + ChanID: pd.ChanID, + FeePerKw: uint32(pd.Amount.ToSatoshis()), + } + } + + return channeldb.LogUpdate{ + LogIndex: pd.LogIndex, + UpdateMsg: msg, + } } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 529db11418..4c4f58f8be 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -2,6 +2,7 @@ package lnwallet import ( "errors" + "fmt" "net" "sync" @@ -11,6 +12,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -25,7 +27,7 @@ type CommitmentType int const ( // CommitmentTypeLegacy is the legacy commitment format with a tweaked // to_remote key. - CommitmentTypeLegacy = iota + CommitmentTypeLegacy CommitmentType = iota // CommitmentTypeTweakless is a newer commitment format where the // to_remote key is static. @@ -49,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 @@ -58,8 +65,11 @@ func (c CommitmentType) HasStaticRemoteKey() bool { case CommitmentTypeTweakless, CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, - CommitmentTypeSimpleTaproot: + CommitmentTypeSimpleTaproot, + CommitmentTypeSimpleTaprootOverlay: + return true + default: return false } @@ -70,8 +80,11 @@ func (c CommitmentType) HasAnchors() bool { switch c { case CommitmentTypeAnchorsZeroFeeHtlcTx, CommitmentTypeScriptEnforcedLease, - CommitmentTypeSimpleTaproot: + CommitmentTypeSimpleTaproot, + CommitmentTypeSimpleTaprootOverlay: + return true + default: return false } @@ -79,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. @@ -95,11 +109,35 @@ func (c CommitmentType) String() string { return "script-enforced-lease" case CommitmentTypeSimpleTaproot: return "simple-taproot" + case CommitmentTypeSimpleTaprootOverlay: + return "simple-taproot-overlay" default: return "invalid" } } +// ReservationState is a type that represents the current state of a channel +// reservation within the funding workflow. +type ReservationState int + +const ( + // WaitingToSend is the state either the funder/fundee is in after + // creating a reservation, but hasn't sent a message yet. + WaitingToSend ReservationState = iota + + // SentOpenChannel is the state the funder is in after sending the + // OpenChannel message. + SentOpenChannel + + // SentAcceptChannel is the state the fundee is in after sending the + // AcceptChannel message. + SentAcceptChannel + + // SentFundingCreated is the state the funder is in after sending the + // FundingCreated message. + SentFundingCreated +) + // ChannelContribution is the primary constituent of the funding workflow // within lnwallet. Each side first exchanges their respective contributions // along with channel specific parameters like the min fee/KB. Once @@ -223,6 +261,8 @@ type ChannelReservation struct { nextRevocationKeyLoc keychain.KeyLocator musigSessions *MusigPairSession + + state ReservationState } // NewChannelReservation creates a new channel reservation. This function is @@ -261,7 +301,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // addition to the two anchor outputs. feeMSat := lnwire.NewMSatFromSatoshis(commitFee) if req.CommitType.HasAnchors() { - feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) + feeMSat += 2 * lnwire.NewMSatFromSatoshis(AnchorSize) } // Used to cut down on verbosity. @@ -399,7 +439,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.FrozenBit } - if req.CommitType == CommitmentTypeSimpleTaproot { + if req.CommitType.IsTaproot() { chanType |= channeldb.SimpleTaprootFeatureBit } @@ -415,6 +455,18 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.ScidAliasFeatureBit } + 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 + } + return &ChannelReservation{ ourContribution: &ChannelContribution{ FundingAmount: ourBalance.ToSatoshis(), @@ -448,12 +500,14 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, InitialLocalBalance: ourBalance, InitialRemoteBalance: theirBalance, Memo: req.Memo, + TapscriptRoot: req.TapscriptRoot, }, pushMSat: req.PushMSat, pendingChanID: req.PendingChanID, reservationID: id, wallet: wallet, chanFunder: req.ChanFunder, + state: WaitingToSend, }, nil } @@ -465,6 +519,22 @@ func (r *ChannelReservation) AddAlias(scid lnwire.ShortChannelID) { r.partialState.ShortChannelID = scid } +// SetState sets the ReservationState. +func (r *ChannelReservation) SetState(state ReservationState) { + r.Lock() + defer r.Unlock() + + r.state = state +} + +// State returns the current ReservationState. +func (r *ChannelReservation) State() ReservationState { + r.RLock() + defer r.RUnlock() + + return r.state +} + // SetNumConfsRequired sets the number of confirmations that are required for // the ultimate funding transaction before the channel can be considered open. // This is distinct from the main reservation workflow as it allows @@ -605,12 +675,15 @@ func (r *ChannelReservation) IsCannedShim() bool { } // ProcessPsbt continues a previously paused funding flow that involves PSBT to -// construct the funding transaction. This method can be called once the PSBT is -// finalized and the signed transaction is available. -func (r *ChannelReservation) ProcessPsbt() error { +// construct the funding transaction. This method can be called once the PSBT +// is finalized and the signed transaction is available. +func (r *ChannelReservation) ProcessPsbt( + auxFundingDesc fn.Option[AuxFundingDesc]) error { + errChan := make(chan error, 1) r.wallet.msgChan <- &continueContributionMsg{ + auxFundingDesc: auxFundingDesc, pendingFundingID: r.reservationID, err: errChan, } @@ -712,8 +785,10 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Sc // available via the .OurSignatures() method. As this method should only be // called as a response to a single funder channel, only a commitment signature // will be populated. -func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint, - commitSig input.Signature) (*channeldb.OpenChannel, error) { +func (r *ChannelReservation) CompleteReservationSingle( + fundingPoint *wire.OutPoint, commitSig input.Signature, + auxFundingDesc fn.Option[AuxFundingDesc]) (*channeldb.OpenChannel, + error) { errChan := make(chan error, 1) completeChan := make(chan *channeldb.OpenChannel, 1) @@ -723,6 +798,7 @@ func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoi fundingOutpoint: fundingPoint, theirCommitmentSig: commitSig, completeChan: completeChan, + auxFundingDesc: auxFundingDesc, err: errChan, } @@ -808,6 +884,42 @@ func (r *ChannelReservation) Cancel() error { return <-errChan } +// ChanState the current open channel state. +func (r *ChannelReservation) ChanState() *channeldb.OpenChannel { + r.RLock() + defer r.RUnlock() + + return r.partialState +} + +// CommitmentKeyRings returns the local+remote key ring used for the very first +// commitment transaction both parties. +// +//nolint:lll +func (r *ChannelReservation) CommitmentKeyRings() lntypes.Dual[CommitmentKeyRing] { + r.RLock() + defer r.RUnlock() + + chanType := r.partialState.ChanType + ourChanCfg := r.ourContribution.ChannelConfig + theirChanCfg := r.theirContribution.ChannelConfig + + localKeys := DeriveCommitmentKeys( + r.ourContribution.FirstCommitmentPoint, lntypes.Local, chanType, + ourChanCfg, theirChanCfg, + ) + + remoteKeys := DeriveCommitmentKeys( + r.theirContribution.FirstCommitmentPoint, lntypes.Remote, + chanType, ourChanCfg, theirChanCfg, + ) + + return lntypes.Dual[CommitmentKeyRing]{ + Local: *localKeys, + Remote: *remoteKeys, + } +} + // VerifyConstraints is a helper function that can be used to check the sanity // of various channel constraints. func VerifyConstraints(bounds *channeldb.ChannelStateBounds, diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index 2424757f93..2296e17031 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -45,17 +45,17 @@ type VerifyJob struct { // party's update log. HtlcIndex uint64 - // Cancel is a channel that should be closed if the caller wishes to + // Cancel is a channel that is closed by the caller if they wish to // cancel all pending verification jobs part of a single batch. This - // channel is to be closed in the case that a single signature in a - // batch has been returned as invalid, as there is no need to verify - // the remainder of the signatures. - Cancel chan struct{} + // channel is closed in the case that a single signature in a batch has + // been returned as invalid, as there is no need to verify the remainder + // of the signatures. + Cancel <-chan struct{} // ErrResp is the channel that the result of the signature verification // is to be sent over. In the see that the signature is valid, a nil // error will be passed. Otherwise, a concrete error detailing the - // issue will be passed. + // issue will be passed. This channel MUST be buffered. ErrResp chan *HtlcIndexErr } @@ -86,12 +86,13 @@ type SignJob struct { // transaction being signed. OutputIndex int32 - // Cancel is a channel that should be closed if the caller wishes to - // abandon all pending sign jobs part of a single batch. - Cancel chan struct{} + // Cancel is a channel that is closed by the caller if they wish to + // abandon all pending sign jobs part of a single batch. This should + // never be closed by the validator. + Cancel <-chan struct{} // Resp is the channel that the response to this particular SignJob - // will be sent over. + // will be sent over. This channel MUST be buffered. // // TODO(roasbeef): actually need to allow caller to set, need to retain // order mark commit sig as special diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index bcb396cafa..9910caefe5 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -34,6 +34,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -940,6 +941,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, fundingPoint := aliceChanReservation.FundingOutpoint() _, err = bobChanReservation.CompleteReservationSingle( fundingPoint, aliceCommitSig, + fn.None[lnwallet.AuxFundingDesc](), ) require.NoError(t, err, "bob unable to consume single reservation") @@ -1756,97 +1758,88 @@ func testPublishTransaction(r *rpctest.Harness, tx3, tx3Spend *wire.MsgTx ) t.Run("rbf_tests", func(t *testing.T) { - for _, rbf := range []bool{false, true} { - // Now we'll try to double spend an output with a - // different transaction. Create a new tx and publish - // it. This is the output we'll try to double spend. - tx3 = newTx(t, r, keyDesc.PubKey, alice, false) - err := alice.PublishTransaction(tx3, labels.External) - require.NoError(t, err) - - // Mine the transaction. - err = mineAndAssert(r, tx3) - require.NoError(t, err) + // Starting with bitcoind v28.0 and later, mempool full RBF is + // turned on, so there's no way to _not_ signal RBF anymore. + const rbf = true + + // Now we'll try to double spend an output with a + // different transaction. Create a new tx and publish + // it. This is the output we'll try to double spend. + tx3 = newTx(t, r, keyDesc.PubKey, alice, false) + err := alice.PublishTransaction(tx3, labels.External) + require.NoError(t, err) - // Now we create a transaction that spends the output - // from the tx just mined. - tx4, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, - keyDesc.PubKey, txFee, rbf, - ) - require.NoError(t, err) + // Mine the transaction. + err = mineAndAssert(r, tx3) + require.NoError(t, err) - // This should be accepted into the mempool. - err = alice.PublishTransaction(tx4, labels.External) - require.NoError(t, err) + // Now we create a transaction that spends the output + // from the tx just mined. + tx4, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + keyDesc.PubKey, txFee, rbf, + ) + require.NoError(t, err) - // Keep track of the last successfully published tx to - // spend tx3. - tx3Spend = tx4 + // This should be accepted into the mempool. + err = alice.PublishTransaction(tx4, labels.External) + require.NoError(t, err) - txid4 := tx4.TxHash() - err = waitForMempoolTx(r, &txid4) - require.NoError(t, err, "tx not relayed to miner") + // Keep track of the last successfully published tx to + // spend tx3. + tx3Spend = tx4 - // Create a new key we'll pay to, to ensure we create a - // unique transaction. - keyDesc2, err := alice.DeriveNextKey( - keychain.KeyFamilyMultiSig, - ) - require.NoError(t, err, "unable to obtain public key") + txid4 := tx4.TxHash() + err = waitForMempoolTx(r, &txid4) + require.NoError(t, err, "tx not relayed to miner") - // Create a new transaction that spends the output from - // tx3, and that pays to a different address. - tx5, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, - keyDesc2.PubKey, txFee, rbf, - ) - require.NoError(t, err) + // Create a new key we'll pay to, to ensure we create a + // unique transaction. + keyDesc2, err := alice.DeriveNextKey( + keychain.KeyFamilyMultiSig, + ) + require.NoError(t, err, "unable to obtain public key") - err = alice.PublishTransaction(tx5, labels.External) + // Create a new transaction that spends the output from + // tx3, and that pays to a different address. + tx5, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + keyDesc2.PubKey, txFee, rbf, + ) + require.NoError(t, err) - // If RBF is not enabled, we expect this to be rejected - // because it is a double spend. - expectedErr := lnwallet.ErrDoubleSpend + err = alice.PublishTransaction(tx5, labels.External) - // If RBF is enabled, we expect it to be rejected - // because it doesn't pay enough fees. - if rbf { - expectedErr = chain.ErrInsufficientFee - } + // We expect it to be rejected/ because it doesn't pay enough + // fees. + expectedErr := chain.ErrInsufficientFee - // Assert the expected error. - require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf) + // Assert the expected error. + require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf) - // Create another transaction that spends the same - // output, but has a higher fee. We expect also this tx - // to be rejected for non-RBF enabled transactions, - // while it should succeed otherwise. - pubKey3, err := alice.DeriveNextKey( - keychain.KeyFamilyMultiSig, - ) - require.NoError(t, err, "unable to obtain public key") + // Create another transaction that spends the same + // output, but has a higher fee. We expect also this tx + // to be rejected for non-RBF enabled transactions, + // while it should succeed otherwise. + pubKey3, err := alice.DeriveNextKey( + keychain.KeyFamilyMultiSig, + ) + require.NoError(t, err, "unable to obtain public key") - tx6, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, - pubKey3.PubKey, 2*txFee, rbf, - ) - require.NoError(t, err) + tx6, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + pubKey3.PubKey, 2*txFee, rbf, + ) + require.NoError(t, err) - // Expect rejection in non-RBF case. - expErr := lnwallet.ErrDoubleSpend - if rbf { - // Expect success in rbf case. - expErr = nil - tx3Spend = tx6 - } - err = alice.PublishTransaction(tx6, labels.External) - require.ErrorIs(t, err, expErr) + // Expect rejection in non-RBF case. + tx3Spend = tx6 + err = alice.PublishTransaction(tx6, labels.External) + require.NoError(t, err) - // Mine the tx spending tx3. - err = mineAndAssert(r, tx3Spend) - require.NoError(t, err) - } + // Mine the tx spending tx3. + err = mineAndAssert(r, tx3Spend) + require.NoError(t, err) }) t.Run("tx_double_spend", func(t *testing.T) { @@ -3069,9 +3062,9 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness, ) } -// TestInterfaces tests all registered interfaces with a unified set of tests -// which exercise each of the required methods found within the WalletController -// interface. +// TestLightningWallet tests all registered interfaces with a unified set of +// tests which exercise each of the required methods found within the +// WalletController interface. // // NOTE: In the future, when additional implementations of the WalletController // interface have been implemented, in order to ensure the new concrete diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index 1e5af77031..2253232962 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -1,6 +1,8 @@ package lnwallet import ( + "bytes" + "context" "crypto/rand" "encoding/binary" "encoding/hex" @@ -14,12 +16,15 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -99,6 +104,10 @@ var ( bobDustLimit = btcutil.Amount(1300) testChannelCapacity float64 = 10 + + // ctxb is a context that will never be cancelled, that is used in + // place of a real quit context. + ctxb = context.Background() ) // CreateTestChannels creates to fully populated channels to be used within @@ -258,7 +267,7 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, commitFee := calcStaticFee(chanType, 0) var anchorAmt btcutil.Amount if chanType.HasAnchors() { - anchorAmt += 2 * anchorSize + anchorAmt += 2 * AnchorSize } aliceBalance := lnwire.NewMSatFromSatoshis( @@ -348,14 +357,33 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, Packager: channeldb.NewChannelPackager(shortChanID), } + // If the channel type has a tapscript root, then we'll also specify + // one here to apply to both the channels. + if chanType.HasTapscriptRoot() { + var tapscriptRoot chainhash.Hash + _, err := io.ReadFull(rand.Reader, tapscriptRoot[:]) + if err != nil { + return nil, nil, err + } + + someRoot := fn.Some(tapscriptRoot) + + aliceChannelState.TapscriptRoot = someRoot + bobChannelState.TapscriptRoot = someRoot + } + aliceSigner := input.NewMockSigner(aliceKeys, nil) bobSigner := input.NewMockSigner(bobKeys, nil) // TODO(roasbeef): make mock version of pre-image store + auxSigner := NewDefaultAuxSignerMock(t) + alicePool := NewSigPool(1, aliceSigner) channelAlice, err := NewLightningChannel( aliceSigner, aliceChannelState, alicePool, + WithLeafStore(&MockAuxLeafStore{}), + WithAuxSigner(auxSigner), ) if err != nil { return nil, nil, err @@ -370,6 +398,8 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType, bobPool := NewSigPool(1, bobSigner) channelBob, err := NewLightningChannel( bobSigner, bobChannelState, bobPool, + WithLeafStore(&MockAuxLeafStore{}), + WithAuxSigner(auxSigner), ) if err != nil { return nil, nil, err @@ -532,7 +562,7 @@ func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount // pending updates. This method is useful when testing interactions between two // live state machines. func ForceStateTransition(chanA, chanB *LightningChannel) error { - aliceNewCommit, err := chanA.SignNextCommitment() + aliceNewCommit, err := chanA.SignNextCommitment(ctxb) if err != nil { return err } @@ -545,12 +575,12 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if err != nil { return err } - bobNewCommit, err := chanB.SignNextCommitment() + bobNewCommit, err := chanB.SignNextCommitment(ctxb) if err != nil { return err } - _, _, _, _, err = chanA.ReceiveRevocation(bobRevocation) + _, _, err = chanA.ReceiveRevocation(bobRevocation) if err != nil { return err } @@ -563,10 +593,45 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if err != nil { return err } - _, _, _, _, err = chanB.ReceiveRevocation(aliceRevocation) + _, _, err = chanB.ReceiveRevocation(aliceRevocation) if err != nil { return err } return nil } + +func NewDefaultAuxSignerMock(t *testing.T) *MockAuxSigner { + auxSigner := NewAuxSignerMock(EmptyMockJobHandler) + + type testSigBlob struct { + BlobInt tlv.RecordT[tlv.TlvType65634, uint16] + } + + var sigBlobBuf bytes.Buffer + sigBlob := testSigBlob{ + BlobInt: tlv.NewPrimitiveRecord[tlv.TlvType65634, uint16](5), + } + tlvStream, err := tlv.NewStream(sigBlob.BlobInt.Record()) + require.NoError(t, err, "unable to create tlv stream") + require.NoError(t, tlvStream.Encode(&sigBlobBuf)) + + auxSigner.On( + "SubmitSecondLevelSigBatch", mock.Anything, mock.Anything, + mock.Anything, + ).Return(nil) + auxSigner.On( + "PackSigs", mock.Anything, + ).Return(fn.Ok(fn.Some(sigBlobBuf.Bytes()))) + auxSigner.On( + "UnpackSigs", mock.Anything, + ).Return(fn.Ok([]fn.Option[tlv.Blob]{ + fn.Some(sigBlobBuf.Bytes()), + })) + auxSigner.On( + "VerifySecondLevelSigs", mock.Anything, mock.Anything, + mock.Anything, + ).Return(nil) + + return auxSigner +} diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 1cf954d3cb..da86650bc6 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" ) const ( @@ -50,8 +51,8 @@ var ( // - func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay, - leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) ( - *wire.MsgTx, error) { + leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey, + auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) { // Create a version two transaction (as the success version of this // spends an output with a CSV timeout). @@ -71,7 +72,7 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, // HTLC outputs. scriptInfo, err := SecondLevelHtlcScript( chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, + leaseExpiry, auxLeaf, ) if err != nil { return nil, err @@ -110,7 +111,8 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, cltvExpiry, csvDelay, leaseExpiry uint32, - revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) { + revocationKey, delayKey *btcec.PublicKey, + auxLeaf input.AuxTapLeaf) (*wire.MsgTx, error) { // Create a version two transaction (as the success version of this // spends an output with a CSV timeout), and set the lock-time to the @@ -134,7 +136,7 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, // HTLC outputs. scriptInfo, err := SecondLevelHtlcScript( chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, + leaseExpiry, auxLeaf, ) if err != nil { return nil, err diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 4c7fbed53b..7912772962 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -21,6 +21,7 @@ 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/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -356,7 +357,7 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { // Execute commit dance to arrive at the point where the local node has // received the test commitment and the remote signature. - localNewCommit, err := localChannel.SignNextCommitment() + localNewCommit, err := localChannel.SignNextCommitment(ctxb) require.NoError(t, err, "local unable to sign commitment") err = remoteChannel.ReceiveNewCommitment(localNewCommit.CommitSigs) @@ -365,10 +366,10 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { revMsg, _, _, err := remoteChannel.RevokeCurrentCommitment() require.NoError(t, err) - _, _, _, _, err = localChannel.ReceiveRevocation(revMsg) + _, _, err = localChannel.ReceiveRevocation(revMsg) require.NoError(t, err) - remoteNewCommit, err := remoteChannel.SignNextCommitment() + remoteNewCommit, err := remoteChannel.SignNextCommitment(ctxb) require.NoError(t, err) require.Equal( @@ -405,6 +406,8 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { hex.EncodeToString(txBytes.Bytes()), ) + resolutions := forceCloseSum.ContractResolutions.UnwrapOrFail(t) + // Obtain the second level transactions that the local node's channel // state machine has produced. Store them in a map indexed by commit tx // output index. Also complete the second level transaction with the @@ -418,7 +421,8 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { secondLevelTxes[index] = tx } - for _, r := range forceCloseSum.HtlcResolutions.IncomingHTLCs { + htlcResolutions := resolutions.HtlcResolutions + for _, r := range htlcResolutions.IncomingHTLCs { successTx := r.SignedSuccessTx witnessScript := successTx.TxIn[0].Witness[4] var hash160 [20]byte @@ -427,7 +431,7 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { successTx.TxIn[0].Witness[3] = preimage[:] storeTx(r.HtlcPoint().Index, successTx) } - for _, r := range forceCloseSum.HtlcResolutions.OutgoingHTLCs { + for _, r := range htlcResolutions.OutgoingHTLCs { storeTx(r.HtlcPoint().Index, r.SignedTimeoutTx) } @@ -631,6 +635,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { commitmentTx, err := CreateCommitTx( channelType, *fakeFundingTxIn, keyRing, aliceChanCfg, bobChanCfg, channelBalance, channelBalance, 0, true, 0, + fn.None[CommitAuxLeaves](), ) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) @@ -925,7 +930,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp var anchorAmt btcutil.Amount if chanType.HasAnchors() { - anchorAmt = 2 * anchorSize + anchorAmt = 2 * AnchorSize } remoteCommitTx, localCommitTx, err := CreateCommitmentTxns( @@ -1016,9 +1021,12 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2, }, nil) + auxSigner := NewDefaultAuxSignerMock(t) remotePool := NewSigPool(1, remoteSigner) channelRemote, err := NewLightningChannel( remoteSigner, remoteChannelState, remotePool, + WithLeafStore(&MockAuxLeafStore{}), + WithAuxSigner(auxSigner), ) require.NoError(t, err) require.NoError(t, remotePool.Start()) @@ -1026,6 +1034,8 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp localPool := NewSigPool(1, localSigner) channelLocal, err := NewLightningChannel( localSigner, localChannelState, localPool, + WithLeafStore(&MockAuxLeafStore{}), + WithAuxSigner(auxSigner), ) require.NoError(t, err) require.NoError(t, localPool.Start()) diff --git a/lnwallet/update_log.go b/lnwallet/update_log.go index 5b9d6c5657..42e5373a22 100644 --- a/lnwallet/update_log.go +++ b/lnwallet/update_log.go @@ -29,16 +29,16 @@ type updateLog struct { // List is the updatelog itself, we embed this value so updateLog has // access to all the method of a list.List. - *fn.List[*PaymentDescriptor] + *fn.List[*paymentDescriptor] // updateIndex maps a `logIndex` to a particular update entry. It // deals with the four update types: // `Fail|MalformedFail|Settle|FeeUpdate` - updateIndex map[uint64]*fn.Node[*PaymentDescriptor] + updateIndex map[uint64]*fn.Node[*paymentDescriptor] // htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the // `Add` update. - htlcIndex map[uint64]*fn.Node[*PaymentDescriptor] + htlcIndex map[uint64]*fn.Node[*paymentDescriptor] // modifiedHtlcs is a set that keeps track of all the current modified // htlcs, hence update types `Fail|MalformedFail|Settle`. A modified @@ -50,9 +50,9 @@ type updateLog struct { // newUpdateLog creates a new updateLog instance. func newUpdateLog(logIndex, htlcCounter uint64) *updateLog { return &updateLog{ - List: fn.NewList[*PaymentDescriptor](), - updateIndex: make(map[uint64]*fn.Node[*PaymentDescriptor]), - htlcIndex: make(map[uint64]*fn.Node[*PaymentDescriptor]), + List: fn.NewList[*paymentDescriptor](), + updateIndex: make(map[uint64]*fn.Node[*paymentDescriptor]), + htlcIndex: make(map[uint64]*fn.Node[*paymentDescriptor]), logIndex: logIndex, htlcCounter: htlcCounter, modifiedHtlcs: fn.NewSet[uint64](), @@ -64,7 +64,7 @@ func newUpdateLog(logIndex, htlcCounter uint64) *updateLog { // state. This function differs from appendHtlc in that it won't increment // either of log's counters. If the HTLC is already present, then it is // ignored. -func (u *updateLog) restoreHtlc(pd *PaymentDescriptor) { +func (u *updateLog) restoreHtlc(pd *paymentDescriptor) { if _, ok := u.htlcIndex[pd.HtlcIndex]; ok { return } @@ -74,7 +74,7 @@ func (u *updateLog) restoreHtlc(pd *PaymentDescriptor) { // appendUpdate appends a new update to the tip of the updateLog. The entry is // also added to index accordingly. -func (u *updateLog) appendUpdate(pd *PaymentDescriptor) { +func (u *updateLog) appendUpdate(pd *paymentDescriptor) { u.updateIndex[u.logIndex] = u.PushBack(pd) u.logIndex++ } @@ -82,13 +82,13 @@ func (u *updateLog) appendUpdate(pd *PaymentDescriptor) { // restoreUpdate appends a new update to the tip of the updateLog. The entry is // also added to index accordingly. This function differs from appendUpdate in // that it won't increment the log index counter. -func (u *updateLog) restoreUpdate(pd *PaymentDescriptor) { +func (u *updateLog) restoreUpdate(pd *paymentDescriptor) { u.updateIndex[pd.LogIndex] = u.PushBack(pd) } // appendHtlc appends a new HTLC offer to the tip of the update log. The entry // is also added to the offer index accordingly. -func (u *updateLog) appendHtlc(pd *PaymentDescriptor) { +func (u *updateLog) appendHtlc(pd *paymentDescriptor) { u.htlcIndex[u.htlcCounter] = u.PushBack(pd) u.htlcCounter++ @@ -97,7 +97,7 @@ func (u *updateLog) appendHtlc(pd *PaymentDescriptor) { // lookupHtlc attempts to look up an offered HTLC according to its offer // index. If the entry isn't found, then a nil pointer is returned. -func (u *updateLog) lookupHtlc(i uint64) *PaymentDescriptor { +func (u *updateLog) lookupHtlc(i uint64) *paymentDescriptor { htlc, ok := u.htlcIndex[i] if !ok { return nil @@ -145,7 +145,7 @@ func compactLogs(ourLog, theirLog *updateLog, localChainTail, remoteChainTail uint64) { compactLog := func(logA, logB *updateLog) { - var nextA *fn.Node[*PaymentDescriptor] + var nextA *fn.Node[*paymentDescriptor] for e := logA.Front(); e != nil; e = nextA { // Assign next iteration element at top of loop because // we may remove the current element from the list, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 90a0bea2d3..4f2ba25eda 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -23,6 +23,7 @@ import ( "github.com/btcsuite/btcwallet/wallet" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" @@ -31,6 +32,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chanvalidate" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" + "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -89,6 +91,33 @@ func (p *PsbtFundingRequired) Error() string { return ErrPsbtFundingRequired.Error() } +// AuxFundingDesc stores a series of attributes that may be used to modify the +// way the channel funding occurs. This struct contains information that can +// only be derived once both sides have received and sent their contributions +// to the channel (keys, etc.). +type AuxFundingDesc struct { + // CustomFundingBlob is a custom blob that'll be stored in the database + // within the OpenChannel struct. This should represent information + // static to the channel lifetime. + CustomFundingBlob tlv.Blob + + // CustomLocalCommitBlob is a custom blob that'll be stored in the + // first commitment entry for the local party. + CustomLocalCommitBlob tlv.Blob + + // CustomRemoteCommitBlob is a custom blob that'll be stored in the + // first commitment entry for the remote party. + CustomRemoteCommitBlob tlv.Blob + + // LocalInitAuxLeaves is the set of aux leaves that'll be used for our + // very first commitment state. + LocalInitAuxLeaves CommitAuxLeaves + + // RemoteInitAuxLeaves is the set of aux leaves that'll be used for the + // very first commitment state for the remote party. + RemoteInitAuxLeaves CommitAuxLeaves +} + // InitFundingReserveMsg is the first message sent to initiate the workflow // required to open a payment channel with a remote peer. The initial required // parameters are configurable across channels. These parameters are to be @@ -210,6 +239,10 @@ type InitFundingReserveMsg struct { // channel that will be useful to our future selves. Memo []byte + // TapscriptRoot is an optional tapscript root that if provided, will + // be used to create the combined key for musig2 based channels. + TapscriptRoot fn.Option[chainhash.Hash] + // err is a channel in which all errors will be sent across. Will be // nil if this initial set is successful. // @@ -245,7 +278,6 @@ type fundingReserveCancelMsg struct { type addContributionMsg struct { pendingFundingID uint64 - // TODO(roasbeef): Should also carry SPV proofs in we're in SPV mode contribution *ChannelContribution // NOTE: In order to avoid deadlocks, this channel MUST be buffered. @@ -258,6 +290,10 @@ type addContributionMsg struct { type continueContributionMsg struct { pendingFundingID uint64 + // auxFundingDesc is an optional descriptor that contains information + // about the custom channel funding flow. + auxFundingDesc fn.Option[AuxFundingDesc] + // NOTE: In order to avoid deadlocks, this channel MUST be buffered. err chan error } @@ -313,6 +349,10 @@ type addCounterPartySigsMsg struct { type addSingleFunderSigsMsg struct { pendingFundingID uint64 + // auxFundingDesc is an optional descriptor that contains information + // about the custom channel funding flow. + auxFundingDesc fn.Option[AuxFundingDesc] + // fundingOutpoint is the outpoint of the completed funding // transaction as assembled by the workflow initiator. fundingOutpoint *wire.OutPoint @@ -416,8 +456,6 @@ type LightningWallet struct { quit chan struct{} wg sync.WaitGroup - - // TODO(roasbeef): handle wallet lock/unlock } // NewLightningWallet creates/opens and initializes a LightningWallet instance. @@ -462,7 +500,6 @@ func (l *LightningWallet) Startup() error { } l.wg.Add(1) - // TODO(roasbeef): multiple request handlers? go l.requestHandler() return nil @@ -946,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, @@ -1418,7 +1455,6 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, // transaction via coin selection are freed allowing future reservations to // include them. func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMsg) { - // TODO(roasbeef): holding lock too long l.limboMtx.Lock() defer l.limboMtx.Unlock() @@ -1443,11 +1479,6 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs ) } - // TODO(roasbeef): is it even worth it to keep track of unused keys? - - // TODO(roasbeef): Is it possible to mark the unused change also as - // available? - delete(l.fundingLimbo, req.pendingFundingID) pid := pendingReservation.pendingChanID @@ -1464,6 +1495,33 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs req.err <- nil } +// createCommitOpts is a struct that holds the options for creating a new +// commitment transaction. +type createCommitOpts struct { + localAuxLeaves fn.Option[CommitAuxLeaves] + remoteAuxLeaves fn.Option[CommitAuxLeaves] +} + +// defaultCommitOpts returns a new createCommitOpts with default values. +func defaultCommitOpts() createCommitOpts { + return createCommitOpts{} +} + +// WithAuxLeaves is a functional option that can be used to set the aux leaves +// for a new commitment transaction. +func WithAuxLeaves(localLeaves, + remoteLeaves fn.Option[CommitAuxLeaves]) CreateCommitOpt { + + return func(o *createCommitOpts) { + o.localAuxLeaves = localLeaves + o.remoteAuxLeaves = remoteLeaves + } +} + +// CreateCommitOpt is a functional option that can be used to modify the way a +// new commitment transaction is created. +type CreateCommitOpt func(*createCommitOpts) + // CreateCommitmentTxns is a helper function that creates the initial // commitment transaction for both parties. This function is used during the // initial funding workflow as both sides must generate a signature for the @@ -1473,7 +1531,13 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourChanCfg, theirChanCfg *channeldb.ChannelConfig, localCommitPoint, remoteCommitPoint *btcec.PublicKey, fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool, - leaseExpiry uint32) (*wire.MsgTx, *wire.MsgTx, error) { + leaseExpiry uint32, opts ...CreateCommitOpt) (*wire.MsgTx, *wire.MsgTx, + error) { + + options := defaultCommitOpts() + for _, optFunc := range opts { + optFunc(&options) + } localCommitmentKeys := DeriveCommitmentKeys( localCommitPoint, lntypes.Local, chanType, ourChanCfg, @@ -1487,7 +1551,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, theirChanCfg, localBalance, remoteBalance, 0, initiator, - leaseExpiry, + leaseExpiry, options.localAuxLeaves, ) if err != nil { return nil, nil, err @@ -1501,7 +1565,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, theirCommitTx, err := CreateCommitTx( chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, ourChanCfg, remoteBalance, localBalance, 0, !initiator, - leaseExpiry, + leaseExpiry, options.remoteAuxLeaves, ) if err != nil { return nil, nil, err @@ -1594,16 +1658,24 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // and remote key which will be needed to calculate the multisig // funding output in a next step. pendingChanID := pendingReservation.pendingChanID + walletLog.Debugf("Advancing PSBT funding flow for "+ "pending_id(%x), binding keys local_key=%v, "+ "remote_key=%x", pendingChanID, &ourContribution.MultiSigKey, theirContribution.MultiSigKey.PubKey.SerializeCompressed()) + fundingIntent.BindKeys( &ourContribution.MultiSigKey, theirContribution.MultiSigKey.PubKey, ) + // We might have a tapscript root, so we'll bind that now to + // ensure we make the proper funding output. + fundingIntent.BindTapscriptRoot( + pendingReservation.partialState.TapscriptRoot, + ) + // Exit early because we can't continue the funding flow yet. req.err <- &PsbtFundingRequired{ Intent: fundingIntent, @@ -1676,16 +1748,17 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // the commitment transaction for the remote party, and verify their incoming // partial signature. func genMusigSession(ourContribution, theirContribution *ChannelContribution, - signer input.MuSig2Signer, - fundingOutput *wire.TxOut) *MusigPairSession { + signer input.MuSig2Signer, fundingOutput *wire.TxOut, + tapscriptRoot fn.Option[chainhash.Hash]) *MusigPairSession { return NewMusigPairSession(&MusigSessionCfg{ - LocalKey: ourContribution.MultiSigKey, - RemoteKey: theirContribution.MultiSigKey, - LocalNonce: *ourContribution.LocalNonce, - RemoteNonce: *theirContribution.LocalNonce, - Signer: signer, - InputTxOut: fundingOutput, + LocalKey: ourContribution.MultiSigKey, + RemoteKey: theirContribution.MultiSigKey, + LocalNonce: *ourContribution.LocalNonce, + RemoteNonce: *theirContribution.LocalNonce, + Signer: signer, + InputTxOut: fundingOutput, + TapscriptTweak: tapscriptRoot, }) } @@ -1735,6 +1808,7 @@ func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, musigSessions := genMusigSession( ourContribution, theirContribution, l.Cfg.Signer, fundingOutput, + pendingReservation.partialState.TapscriptRoot, ) pendingReservation.musigSessions = musigSessions } @@ -1770,6 +1844,26 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { return } + chanState := pendingReservation.partialState + + // If we have an aux funding desc, then we can use it to populate some + // of the optional, but opaque TLV blobs we'll carry for the channel. + chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomFundingBlob + })(req.auxFundingDesc) + + chanState.LocalCommitment.CustomBlob = fn.MapOption( + func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomLocalCommitBlob + }, + )(req.auxFundingDesc) + + chanState.RemoteCommitment.CustomBlob = fn.MapOption( + func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomRemoteCommitBlob + }, + )(req.auxFundingDesc) + ourContribution := pendingReservation.ourContribution theirContribution := pendingReservation.theirContribution chanPoint := pendingReservation.partialState.FundingOutpoint @@ -1828,7 +1922,6 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { // Store their current commitment point. We'll need this after the // first state transition in order to verify the authenticity of the // revocation. - chanState := pendingReservation.partialState chanState.RemoteCurrentRevocation = theirContribution.FirstCommitmentPoint // Create the txin to our commitment transaction; required to construct @@ -1844,6 +1937,18 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { if pendingReservation.partialState.ChanType.HasLeaseExpiration() { leaseExpiry = pendingReservation.partialState.ThawHeight } + + localAuxLeaves := fn.MapOption( + func(desc AuxFundingDesc) CommitAuxLeaves { + return desc.LocalInitAuxLeaves + }, + )(req.auxFundingDesc) + remoteAuxLeaves := fn.MapOption( + func(desc AuxFundingDesc) CommitAuxLeaves { + return desc.RemoteInitAuxLeaves + }, + )(req.auxFundingDesc) + ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, ourContribution.ChannelConfig, theirContribution.ChannelConfig, @@ -1851,6 +1956,7 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { theirContribution.FirstCommitmentPoint, fundingTxIn, pendingReservation.partialState.ChanType, pendingReservation.partialState.IsInitiator, leaseExpiry, + WithAuxLeaves(localAuxLeaves, remoteAuxLeaves), ) if err != nil { req.err <- err @@ -2102,6 +2208,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, if res.musigSessions == nil { _, fundingOutput, err := input.GenTaprootFundingScript( localKey, remoteKey, channelValue, + res.partialState.TapscriptRoot, ) if err != nil { return err @@ -2110,6 +2217,7 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, res.musigSessions = genMusigSession( res.ourContribution, res.theirContribution, l.Cfg.Signer, fundingOutput, + res.partialState.TapscriptRoot, ) } @@ -2200,9 +2308,6 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // As we're about to broadcast the funding transaction, we'll take note // of the current height for record keeping purposes. - // - // TODO(roasbeef): this info can also be piped into light client's - // basic fee estimation? _, bestHeight, err := l.Cfg.ChainIO.GetBestBlock() if err != nil { msg.err <- err @@ -2263,6 +2368,23 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { defer pendingReservation.Unlock() chanState := pendingReservation.partialState + + // If we have an aux funding desc, then we can use it to populate some + // of the optional, but opaque TLV blobs we'll carry for the channel. + chanState.CustomBlob = fn.MapOption(func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomFundingBlob + })(req.auxFundingDesc) + chanState.LocalCommitment.CustomBlob = fn.MapOption( + func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomLocalCommitBlob + }, + )(req.auxFundingDesc) + chanState.RemoteCommitment.CustomBlob = fn.MapOption( + func(desc AuxFundingDesc) tlv.Blob { + return desc.CustomRemoteCommitBlob + }, + )(req.auxFundingDesc) + chanType := pendingReservation.partialState.ChanType chanState.FundingOutpoint = *req.fundingOutpoint fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil) @@ -2276,6 +2398,18 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { if pendingReservation.partialState.ChanType.HasLeaseExpiration() { leaseExpiry = pendingReservation.partialState.ThawHeight } + + localAuxLeaves := fn.MapOption( + func(desc AuxFundingDesc) CommitAuxLeaves { + return desc.LocalInitAuxLeaves + }, + )(req.auxFundingDesc) + remoteAuxLeaves := fn.MapOption( + func(desc AuxFundingDesc) CommitAuxLeaves { + return desc.RemoteInitAuxLeaves + }, + )(req.auxFundingDesc) + ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, pendingReservation.ourContribution.ChannelConfig, @@ -2284,6 +2418,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { pendingReservation.theirContribution.FirstCommitmentPoint, *fundingTxIn, chanType, pendingReservation.partialState.IsInitiator, leaseExpiry, + WithAuxLeaves(localAuxLeaves, remoteAuxLeaves), ) if err != nil { req.err <- err @@ -2341,11 +2476,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { fundingTxOut *wire.TxOut ) if chanType.IsTaproot() { - fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( //nolint:lll + //nolint:lll + fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( ourKey.PubKey, theirKey.PubKey, channelValue, + pendingReservation.partialState.TapscriptRoot, ) } else { - fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( //nolint:lll + //nolint:lll + fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( ourKey.PubKey.SerializeCompressed(), theirKey.PubKey.SerializeCompressed(), channelValue, ) @@ -2465,9 +2603,19 @@ func initStateHints(commit1, commit2 *wire.MsgTx, func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, fundingTx *wire.MsgTx) error { + var chanOpts []ChannelOpt + l.Cfg.AuxLeafStore.WhenSome(func(s AuxLeafStore) { + chanOpts = append(chanOpts, WithLeafStore(s)) + }) + l.Cfg.AuxSigner.WhenSome(func(s AuxSigner) { + chanOpts = append(chanOpts, WithAuxSigner(s)) + }) + // First, we'll obtain a fully signed commitment transaction so we can // pass into it on the chanvalidate package for verification. - channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil) + channel, err := NewLightningChannel( + l.Cfg.Signer, channelState, nil, chanOpts..., + ) if err != nil { return err } @@ -2482,6 +2630,7 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, if channelState.ChanType.IsTaproot() { fundingScript, _, err = input.GenTaprootFundingScript( localKey, remoteKey, int64(channel.Capacity), + channelState.TapscriptRoot, ) if err != nil { return err diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index 7deb64ae1c..3a475e71ff 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -45,6 +45,10 @@ type CommitSig struct { // being signed for. In this case, the above Sig type MUST be blank. PartialSig OptPartialSigWithNonceTLV + // CustomRecords maps TLV types to byte slices, storing arbitrary data + // intended for inclusion in the ExtraData field. + CustomRecords CustomRecords + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -53,9 +57,7 @@ type CommitSig struct { // NewCommitSig creates a new empty CommitSig message. func NewCommitSig() *CommitSig { - return &CommitSig{ - ExtraData: make([]byte, 0), - } + return &CommitSig{} } // A compile time check to ensure CommitSig implements the lnwire.Message @@ -67,34 +69,37 @@ var _ Message = (*CommitSig)(nil) // // This is part of the lnwire.Message interface. func (c *CommitSig) Decode(r io.Reader, pver uint32) error { + // msgExtraData is a temporary variable used to read the message extra + // data field from the reader. + var msgExtraData ExtraOpaqueData + err := ReadElements(r, &c.ChanID, &c.CommitSig, &c.HtlcSigs, + &msgExtraData, ) if err != nil { return err } - var tlvRecords ExtraOpaqueData - if err := ReadElements(r, &tlvRecords); err != nil { - return err - } - + // Extract TLV records from the extra data field. partialSig := c.PartialSig.Zero() - typeMap, err := tlvRecords.ExtractRecords(&partialSig) + + customRecords, parsed, extraData, err := ParseAndExtractCustomRecords( + msgExtraData, &partialSig, + ) if err != nil { return err } // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[c.PartialSig.TlvType()]; ok && val == nil { + if _, ok := parsed[partialSig.TlvType()]; ok { c.PartialSig = tlv.SomeRecordT(partialSig) } - if len(tlvRecords) != 0 { - c.ExtraData = tlvRecords - } + c.CustomRecords = customRecords + c.ExtraData = extraData return nil } @@ -108,7 +113,10 @@ func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { c.PartialSig.WhenSome(func(sig PartialSigWithNonceTLV) { recordProducers = append(recordProducers, &sig) }) - err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + + extraData, err := MergeAndEncode( + recordProducers, c.ExtraData, c.CustomRecords, + ) if err != nil { return err } @@ -125,7 +133,7 @@ func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { return err } - return WriteBytes(w, c.ExtraData) + return WriteBytes(w, extraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/commit_sig_test.go b/lnwire/commit_sig_test.go new file mode 100644 index 0000000000..0772a2fb83 --- /dev/null +++ b/lnwire/commit_sig_test.go @@ -0,0 +1,168 @@ +package lnwire + +import ( + "bytes" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// testCase is a test case for the CommitSig message. +type commitSigTestCase struct { + // Msg is the message to be encoded and decoded. + Msg CommitSig + + // ExpectEncodeError is a flag that indicates whether we expect the + // encoding of the message to fail. + ExpectEncodeError bool +} + +// generateCommitSigTestCases generates a set of CommitSig message test cases. +func generateCommitSigTestCases(t *testing.T) []commitSigTestCase { + // Firstly, we'll set basic values for the message fields. + // + // Generate random channel ID. + chanIDBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var chanID ChannelID + copy(chanID[:], chanIDBytes) + + // Generate random commit sig. + commitSigBytes, err := generateRandomBytes(64) + require.NoError(t, err) + + sig, err := NewSigFromSchnorrRawSignature(commitSigBytes) + require.NoError(t, err) + + sigScalar := new(btcec.ModNScalar) + sigScalar.SetByteSlice(sig.RawBytes()) + + var nonce [musig2.PubNonceSize]byte + copy(nonce[:], commitSigBytes) + + sigWithNonce := NewPartialSigWithNonce(nonce, *sigScalar) + partialSig := MaybePartialSigWithNonce(sigWithNonce) + + // Define custom records. + recordKey1 := uint64(MinCustomRecordsTlvType + 1) + recordValue1, err := generateRandomBytes(10) + require.NoError(t, err) + + recordKey2 := uint64(MinCustomRecordsTlvType + 2) + recordValue2, err := generateRandomBytes(10) + require.NoError(t, err) + + customRecords := CustomRecords{ + recordKey1: recordValue1, + recordKey2: recordValue2, + } + + // Construct an instance of extra data that contains records with TLV + // types below the minimum custom records threshold and that lack + // corresponding fields in the message struct. Content should persist in + // the extra data field after encoding and decoding. + var ( + recordBytes45 = []byte("recordBytes45") + tlvRecord45 = tlv.NewPrimitiveRecord[tlv.TlvType45]( + recordBytes45, + ) + + recordBytes55 = []byte("recordBytes55") + tlvRecord55 = tlv.NewPrimitiveRecord[tlv.TlvType55]( + recordBytes55, + ) + ) + + var extraData ExtraOpaqueData + err = extraData.PackRecords( + []tlv.RecordProducer{&tlvRecord45, &tlvRecord55}..., + ) + require.NoError(t, err) + + invalidCustomRecords := CustomRecords{ + MinCustomRecordsTlvType - 1: recordValue1, + } + + return []commitSigTestCase{ + { + Msg: CommitSig{ + ChanID: chanID, + CommitSig: sig, + PartialSig: partialSig, + CustomRecords: customRecords, + ExtraData: extraData, + }, + }, + // Add a test case where the blinding point field is not + // populated. + { + Msg: CommitSig{ + ChanID: chanID, + CommitSig: sig, + CustomRecords: customRecords, + }, + }, + // Add a test case where the custom records field is not + // populated. + { + Msg: CommitSig{ + ChanID: chanID, + CommitSig: sig, + PartialSig: partialSig, + }, + }, + // Add a case where the custom records are invalid. + { + Msg: CommitSig{ + ChanID: chanID, + CommitSig: sig, + PartialSig: partialSig, + CustomRecords: invalidCustomRecords, + }, + ExpectEncodeError: true, + }, + } +} + +// TestCommitSigEncodeDecode tests CommitSig message encoding and decoding for +// all supported field values. +func TestCommitSigEncodeDecode(t *testing.T) { + t.Parallel() + + // Generate test cases. + testCases := generateCommitSigTestCases(t) + + // Execute test cases. + for tcIdx, tc := range testCases { + t.Run(fmt.Sprintf("testcase-%d", tcIdx), func(t *testing.T) { + // Encode test case message. + var buf bytes.Buffer + err := tc.Msg.Encode(&buf, 0) + + // Check if we expect an encoding error. + if tc.ExpectEncodeError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Decode the encoded message bytes message. + var actualMsg CommitSig + decodeReader := bytes.NewReader(buf.Bytes()) + err = actualMsg.Decode(decodeReader, 0) + require.NoError(t, err) + + // The signature type isn't serialized. + actualMsg.CommitSig.ForceSchnorr() + + // Compare the two messages to ensure equality. + require.Equal(t, tc.Msg, actualMsg) + }) + } +} diff --git a/lnwire/custom_records.go b/lnwire/custom_records.go new file mode 100644 index 0000000000..8177cbe821 --- /dev/null +++ b/lnwire/custom_records.go @@ -0,0 +1,279 @@ +package lnwire + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // MinCustomRecordsTlvType is the minimum custom records TLV type as + // defined in BOLT 01. + MinCustomRecordsTlvType = 65536 +) + +// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types +// which must be greater than or equal to MinCustomRecordsTlvType. +type CustomRecords map[uint64][]byte + +// NewCustomRecords creates a new CustomRecords instance from a +// tlv.TypeMap. +func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) { + // Make comparisons in unit tests easy by returning nil if the map is + // empty. + if len(tlvMap) == 0 { + return nil, nil + } + + customRecords := make(CustomRecords, len(tlvMap)) + for k, v := range tlvMap { + customRecords[uint64(k)] = v + } + + // Validate the custom records. + err := customRecords.Validate() + if err != nil { + return nil, fmt.Errorf("custom records from tlv map "+ + "validation error: %w", err) + } + + return customRecords, nil +} + +// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob. +func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) { + return ParseCustomRecordsFrom(bytes.NewReader(b)) +} + +// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader. +func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) { + typeMap, err := DecodeRecords(r) + if err != nil { + return nil, fmt.Errorf("error decoding HTLC record: %w", err) + } + + return NewCustomRecords(typeMap) +} + +// Validate checks that all custom records are in the custom type range. +func (c CustomRecords) Validate() error { + if c == nil { + return nil + } + + for key := range c { + if key < MinCustomRecordsTlvType { + return fmt.Errorf("custom records entry with TLV "+ + "type below min: %d", MinCustomRecordsTlvType) + } + } + + return nil +} + +// Copy returns a copy of the custom records. +func (c CustomRecords) Copy() CustomRecords { + if c == nil { + return nil + } + + customRecords := make(CustomRecords, len(c)) + for k, v := range c { + customRecords[k] = v + } + + return customRecords +} + +// MergedCopy creates a copy of the records and merges them with the given +// records. If the same key is present in both sets, the value from the other +// records will be used. +func (c CustomRecords) MergedCopy(other CustomRecords) CustomRecords { + copiedRecords := make(CustomRecords, len(c)) + for k, v := range c { + copiedRecords[k] = v + } + + for k, v := range other { + copiedRecords[k] = v + } + + return copiedRecords +} + +// ExtendRecordProducers extends the given records slice with the custom +// records. The resultant records slice will be sorted if the given records +// slice contains TLV types greater than or equal to MinCustomRecordsTlvType. +func (c CustomRecords) ExtendRecordProducers( + producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) { + + // If the custom records are nil or empty, there is nothing to do. + if len(c) == 0 { + return producers, nil + } + + // Validate the custom records. + err := c.Validate() + if err != nil { + return nil, err + } + + // Ensure that the existing records slice TLV types are not also present + // in the custom records. If they are, the resultant extended records + // slice would erroneously contain duplicate TLV types. + for _, rp := range producers { + record := rp.Record() + recordTlvType := uint64(record.Type()) + + _, foundDuplicateTlvType := c[recordTlvType] + if foundDuplicateTlvType { + return nil, fmt.Errorf("custom records contains a TLV "+ + "type that is already present in the "+ + "existing records: %d", recordTlvType) + } + } + + // Convert the custom records map to a TLV record producer slice and + // append them to the exiting records slice. + customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c)) + producers = append(producers, customRecordProducers...) + + // If the records slice which was given as an argument included TLV + // values greater than or equal to the minimum custom records TLV type + // we will sort the extended records slice to ensure that it is ordered + // correctly. + SortProducers(producers) + + return producers, nil +} + +// RecordProducers returns a slice of record producers for the custom records. +func (c CustomRecords) RecordProducers() []tlv.RecordProducer { + // If the custom records are nil or empty, return an empty slice. + if len(c) == 0 { + return nil + } + + // Convert the custom records map to a TLV record producer slice. + records := tlv.MapToRecords(c) + + return RecordsAsProducers(records) +} + +// Serialize serializes the custom records into a byte slice. +func (c CustomRecords) Serialize() ([]byte, error) { + records := tlv.MapToRecords(c) + return EncodeRecords(records) +} + +// SerializeTo serializes the custom records into the given writer. +func (c CustomRecords) SerializeTo(w io.Writer) error { + records := tlv.MapToRecords(c) + return EncodeRecordsTo(w, records) +} + +// ProduceRecordsSorted converts a slice of record producers into a slice of +// records and then sorts it by type. +func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record { + records := fn.Map(func(producer tlv.RecordProducer) tlv.Record { + return producer.Record() + }, recordProducers) + + // Ensure that the set of records are sorted before we attempt to + // decode from the stream, to ensure they're canonical. + tlv.SortRecords(records) + + return records +} + +// SortProducers sorts the given record producers by their type. +func SortProducers(producers []tlv.RecordProducer) { + sort.Slice(producers, func(i, j int) bool { + recordI := producers[i].Record() + recordJ := producers[j].Record() + return recordI.Type() < recordJ.Type() + }) +} + +// TlvMapToRecords converts a TLV map into a slice of records. +func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record { + tlvMapGeneric := make(map[uint64][]byte) + for k, v := range tlvMap { + tlvMapGeneric[uint64(k)] = v + } + + return tlv.MapToRecords(tlvMapGeneric) +} + +// RecordsAsProducers converts a slice of records into a slice of record +// producers. +func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer { + return fn.Map(func(record tlv.Record) tlv.RecordProducer { + return &record + }, records) +} + +// EncodeRecords encodes the given records into a byte slice. +func EncodeRecords(records []tlv.Record) ([]byte, error) { + var buf bytes.Buffer + if err := EncodeRecordsTo(&buf, records); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// EncodeRecordsTo encodes the given records into the given writer. +func EncodeRecordsTo(w io.Writer, records []tlv.Record) error { + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// DecodeRecords decodes the given byte slice into the given records and returns +// the rest as a TLV type map. +func DecodeRecords(r io.Reader, + records ...tlv.Record) (tlv.TypeMap, error) { + + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return nil, err + } + + return tlvStream.DecodeWithParsedTypes(r) +} + +// DecodeRecordsP2P decodes the given byte slice into the given records and +// returns the rest as a TLV type map. This function is identical to +// DecodeRecords except that the record size is capped at 65535. +func DecodeRecordsP2P(r *bytes.Reader, + records ...tlv.Record) (tlv.TypeMap, error) { + + tlvStream, err := tlv.NewStream(records...) + if err != nil { + return nil, err + } + + return tlvStream.DecodeWithParsedTypesP2P(r) +} + +// AssertUniqueTypes asserts that the given records have unique types. +func AssertUniqueTypes(r []tlv.Record) error { + seen := make(fn.Set[tlv.Type], len(r)) + for _, record := range r { + t := record.Type() + if seen.Contains(t) { + return fmt.Errorf("duplicate record type: %d", t) + } + seen.Add(t) + } + + return nil +} diff --git a/lnwire/custom_records_test.go b/lnwire/custom_records_test.go new file mode 100644 index 0000000000..8ff6af10ba --- /dev/null +++ b/lnwire/custom_records_test.go @@ -0,0 +1,248 @@ +package lnwire + +import ( + "bytes" + "testing" + + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCustomRecords tests the custom records serialization and deserialization, +// as well as copying and producing records. +func TestCustomRecords(t *testing.T) { + testCases := []struct { + name string + customTypes tlv.TypeMap + expectedRecords CustomRecords + expectedErr string + }{ + { + name: "empty custom records", + customTypes: tlv.TypeMap{}, + expectedRecords: nil, + }, + { + name: "custom record with invalid type", + customTypes: tlv.TypeMap{ + 123: []byte{1, 2, 3}, + }, + expectedErr: "TLV type below min: 65536", + }, + { + name: "valid custom record", + customTypes: tlv.TypeMap{ + 65536: []byte{1, 2, 3}, + }, + expectedRecords: map[uint64][]byte{ + 65536: {1, 2, 3}, + }, + }, + { + name: "valid custom records, wrong order", + customTypes: tlv.TypeMap{ + 65537: []byte{3, 4, 5}, + 65536: []byte{1, 2, 3}, + }, + expectedRecords: map[uint64][]byte{ + 65536: {1, 2, 3}, + 65537: {3, 4, 5}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + records, err := NewCustomRecords(tc.customTypes) + + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedRecords, records) + + // Serialize, then parse the records again. + blob, err := records.Serialize() + require.NoError(t, err) + + parsedRecords, err := ParseCustomRecords(blob) + require.NoError(t, err) + + require.Equal(t, tc.expectedRecords, parsedRecords) + + // Copy() should also return the same records. + require.Equal( + t, tc.expectedRecords, parsedRecords.Copy(), + ) + + // RecordProducers() should also allow us to serialize + // the records again. + serializedProducers := serializeRecordProducers( + t, parsedRecords.RecordProducers(), + ) + + require.Equal(t, blob, serializedProducers) + }) + } +} + +// TestCustomRecordsExtendRecordProducers tests that we can extend a slice of +// record producers with custom records. +func TestCustomRecordsExtendRecordProducers(t *testing.T) { + testCases := []struct { + name string + existingTypes map[uint64][]byte + customRecords CustomRecords + expectedResult tlv.TypeMap + expectedErr string + }{ + { + name: "normal merge", + existingTypes: map[uint64][]byte{ + 123: {3, 4, 5}, + 345: {1, 2, 3}, + }, + customRecords: CustomRecords{ + 65536: {1, 2, 3}, + }, + expectedResult: tlv.TypeMap{ + 123: {3, 4, 5}, + 345: {1, 2, 3}, + 65536: {1, 2, 3}, + }, + }, + { + name: "duplicates", + existingTypes: map[uint64][]byte{ + 123: {3, 4, 5}, + 345: {1, 2, 3}, + 65536: {1, 2, 3}, + }, + customRecords: CustomRecords{ + 65536: {1, 2, 3}, + }, + expectedErr: "contains a TLV type that is already " + + "present in the existing records: 65536", + }, + { + name: "non custom type in custom records", + existingTypes: map[uint64][]byte{ + 123: {3, 4, 5}, + 345: {1, 2, 3}, + 65536: {1, 2, 3}, + }, + customRecords: CustomRecords{ + 123: {1, 2, 3}, + }, + expectedErr: "TLV type below min: 65536", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + nonCustomRecords := tlv.MapToRecords(tc.existingTypes) + nonCustomProducers := RecordsAsProducers( + nonCustomRecords, + ) + + combined, err := tc.customRecords.ExtendRecordProducers( + nonCustomProducers, + ) + + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + return + } + + require.NoError(t, err) + + serializedProducers := serializeRecordProducers( + t, combined, + ) + + stream, err := tlv.NewStream() + require.NoError(t, err) + + parsedMap, err := stream.DecodeWithParsedTypes( + bytes.NewReader(serializedProducers), + ) + require.NoError(t, err) + + require.Equal(t, tc.expectedResult, parsedMap) + }) + } +} + +// serializeRecordProducers is a helper function that serializes a slice of +// record producers into a byte slice. +func serializeRecordProducers(t *testing.T, + producers []tlv.RecordProducer) []byte { + + tlvRecords := fn.Map(func(p tlv.RecordProducer) tlv.Record { + return p.Record() + }, producers) + + stream, err := tlv.NewStream(tlvRecords...) + require.NoError(t, err) + + var b bytes.Buffer + err = stream.Encode(&b) + require.NoError(t, err) + + return b.Bytes() +} + +func TestCustomRecordsMergedCopy(t *testing.T) { + tests := []struct { + name string + c CustomRecords + other CustomRecords + want CustomRecords + }{ + { + name: "nil records", + want: make(CustomRecords), + }, + { + name: "empty records", + c: make(CustomRecords), + other: make(CustomRecords), + want: make(CustomRecords), + }, + { + name: "distinct records", + c: CustomRecords{ + 1: {1, 2, 3}, + }, + other: CustomRecords{ + 2: {4, 5, 6}, + }, + want: CustomRecords{ + 1: {1, 2, 3}, + 2: {4, 5, 6}, + }, + }, + { + name: "same records, different values", + c: CustomRecords{ + 1: {1, 2, 3}, + }, + other: CustomRecords{ + 1: {4, 5, 6}, + }, + want: CustomRecords{ + 1: {4, 5, 6}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.c.MergedCopy(tt.other) + assert.Equal(t, tt.want, result) + }) + } +} diff --git a/lnwire/encoding.go b/lnwire/encoding.go index e04b2b01d4..72000f81d1 100644 --- a/lnwire/encoding.go +++ b/lnwire/encoding.go @@ -1,5 +1,7 @@ package lnwire +import "github.com/lightningnetwork/lnd/tlv" + // QueryEncoding is an enum-like type that represents exactly how a set data is // encoded on the wire. type QueryEncoding uint8 @@ -15,3 +17,17 @@ const ( // NOTE: this should no longer be used or accepted. EncodingSortedZlib QueryEncoding = 1 ) + +// recordProducer is a simple helper struct that implements the +// tlv.RecordProducer interface. +type recordProducer struct { + record tlv.Record +} + +// Record returns the underlying record. +func (r *recordProducer) Record() tlv.Record { + return r.record +} + +// Ensure that recordProducer implements the tlv.RecordProducer interface. +var _ tlv.RecordProducer = (*recordProducer)(nil) diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 0ebf48f57d..c4ca260e1e 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/tlv" ) @@ -15,6 +16,21 @@ import ( // upgrades to the network in a forwards compatible manner. type ExtraOpaqueData []byte +// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap. +func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) { + // If the tlv map is empty, we'll want to mirror the behavior of + // decoding an empty extra opaque data field (see Decode method). + if len(tlvMap) == 0 { + return make([]byte, 0), nil + } + + // Convert the TLV map into a slice of records. + records := TlvMapToRecords(tlvMap) + + // Encode the records into the extra data byte slice. + return EncodeRecords(records) +} + // Encode attempts to encode the raw extra bytes into the passed io.Writer. func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error { eBytes := []byte((*e)[:]) @@ -25,8 +41,8 @@ func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error { return nil } -// Decode attempts to unpack the raw bytes encoded in the passed io.Reader as a -// set of extra opaque data. +// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as +// a set of extra opaque data. func (e *ExtraOpaqueData) Decode(r io.Reader) error { // First, we'll attempt to read a set of bytes contained within the // passed io.Reader (if any exist). @@ -39,7 +55,7 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error { // This ensures that any struct that embeds this type will properly // store the bytes once this method exits. if len(rawBytes) > 0 { - *e = ExtraOpaqueData(rawBytes) + *e = rawBytes } else { *e = make([]byte, 0) } @@ -50,28 +66,17 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error { // PackRecords attempts to encode the set of tlv records into the target // ExtraOpaqueData instance. The records will be encoded as a raw TLV stream // and stored within the backing slice pointer. -func (e *ExtraOpaqueData) PackRecords(recordProducers ...tlv.RecordProducer) error { - // First, assemble all the records passed in in series. - records := make([]tlv.Record, 0, len(recordProducers)) - for _, producer := range recordProducers { - records = append(records, producer.Record()) - } - - // Ensure that the set of records are sorted before we encode them into - // the stream, to ensure they're canonical. - tlv.SortRecords(records) +func (e *ExtraOpaqueData) PackRecords( + recordProducers ...tlv.RecordProducer) error { - tlvStream, err := tlv.NewStream(records...) + // Assemble all the records passed in series, then encode them. + records := ProduceRecordsSorted(recordProducers...) + encoded, err := EncodeRecords(records) if err != nil { return err } - var extraBytesWriter bytes.Buffer - if err := tlvStream.Encode(&extraBytesWriter); err != nil { - return err - } - - *e = ExtraOpaqueData(extraBytesWriter.Bytes()) + *e = encoded return nil } @@ -80,29 +85,38 @@ func (e *ExtraOpaqueData) PackRecords(recordProducers ...tlv.RecordProducer) err // it were a tlv stream. The set of raw parsed types is returned, and any // passed records (if found in the stream) will be parsed into the proper // tlv.Record. -func (e *ExtraOpaqueData) ExtractRecords(recordProducers ...tlv.RecordProducer) ( - tlv.TypeMap, error) { +func (e *ExtraOpaqueData) ExtractRecords( + recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) { - // First, assemble all the records passed in in series. - records := make([]tlv.Record, 0, len(recordProducers)) - for _, producer := range recordProducers { - records = append(records, producer.Record()) - } + // First, assemble all the records passed in series. + records := ProduceRecordsSorted(recordProducers...) + extraBytesReader := bytes.NewReader(*e) - // Ensure that the set of records are sorted before we attempt to - // decode from the stream, to ensure they're canonical. - tlv.SortRecords(records) + // Since ExtraOpaqueData is provided by a potentially malicious peer, + // pass it into the P2P decoding variant. + return DecodeRecordsP2P(extraBytesReader, records...) +} - extraBytesReader := bytes.NewReader(*e) +// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers +// by interpreting it as a TLV map. +func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) { + var recordProducers []tlv.RecordProducer - tlvStream, err := tlv.NewStream(records...) + // If the instance is nil or empty, return an empty slice. + if e == nil || len(*e) == 0 { + return recordProducers, nil + } + + // Parse the extra opaque data as a TLV map. + tlvMap, err := e.ExtractRecords() if err != nil { return nil, err } - // Since ExtraOpaqueData is provided by a potentially malicious peer, - // pass it into the P2P decoding variant. - return tlvStream.DecodeWithParsedTypesP2P(extraBytesReader) + // Convert the TLV map into a slice of record producers. + records := TlvMapToRecords(tlvMap) + + return RecordsAsProducers(records), nil } // EncodeMessageExtraData encodes the given recordProducers into the given @@ -120,3 +134,116 @@ func EncodeMessageExtraData(extraData *ExtraOpaqueData, // are all properly sorted. return extraData.PackRecords(recordProducers...) } + +// ParseAndExtractCustomRecords parses the given extra data into the passed-in +// records, then returns any remaining records split into custom records and +// extra data. +func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData, + knownRecords ...tlv.RecordProducer) (CustomRecords, + fn.Set[tlv.Type], ExtraOpaqueData, error) { + + extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...) + if err != nil { + return nil, nil, nil, err + } + + // Remove the known and now extracted records from the leftover extra + // data map. + parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords)) + for _, producer := range knownRecords { + r := producer.Record() + + // Only remove the records if it was parsed (remainder is nil). + // We'll just store the type so we can tell the caller which + // records were actually parsed fully. + val, ok := extraDataTlvMap[r.Type()] + if ok && val == nil { + parsedKnownRecords.Add(r.Type()) + delete(extraDataTlvMap, r.Type()) + } + } + + // Any records from the extra data TLV map which are in the custom + // records TLV type range will be included in the custom records field + // and removed from the extra data field. + customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap)) + for k, v := range extraDataTlvMap { + // Skip records that are not in the custom records TLV type + // range. + if k < MinCustomRecordsTlvType { + continue + } + + // Include the record in the custom records map. + customRecordsTlvMap[k] = v + + // Now that the record is included in the custom records map, + // we can remove it from the extra data TLV map. + delete(extraDataTlvMap, k) + } + + // Set the custom records field to the custom records specific TLV + // record map. + customRecords, err := NewCustomRecords(customRecordsTlvMap) + if err != nil { + return nil, nil, nil, err + } + + // Encode the remaining records back into the extra data field. These + // records are not in the custom records TLV type range and do not + // have associated fields in the struct that produced the records. + extraData, err := NewExtraOpaqueData(extraDataTlvMap) + if err != nil { + return nil, nil, nil, err + } + + // Help with unit testing where we might have the empty value (nil) for + // the extra data instead of the default that's returned by the + // constructor (empty slice). + if len(extraData) == 0 { + extraData = nil + } + + return customRecords, parsedKnownRecords, extraData, nil +} + +// MergeAndEncode merges the known records with the extra data and custom +// records, then encodes the merged records into raw bytes. +func MergeAndEncode(knownRecords []tlv.RecordProducer, + extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte, + error) { + + // Construct a slice of all the records that we should include in the + // message extra data field. We will start by including any records from + // the extra data field. + mergedRecords, err := extraData.RecordProducers() + if err != nil { + return nil, err + } + + // Merge the known and extra data records. + mergedRecords = append(mergedRecords, knownRecords...) + + // Include custom records in the extra data wire field if they are + // present. Ensure that the custom records are validated before encoding + // them. + if err := customRecords.Validate(); err != nil { + return nil, fmt.Errorf("custom records validation error: %w", + err) + } + + // Extend the message extra data records slice with TLV records from the + // custom records field. + mergedRecords = append( + mergedRecords, customRecords.RecordProducers()..., + ) + + // Now we can sort the records and make sure there are no records with + // the same type that would collide when encoding. + sortedRecords := ProduceRecordsSorted(mergedRecords...) + if err := AssertUniqueTypes(sortedRecords); err != nil { + return nil, err + } + + return EncodeRecords(sortedRecords) +} diff --git a/lnwire/extra_bytes_test.go b/lnwire/extra_bytes_test.go index fd9f28841d..79c913362e 100644 --- a/lnwire/extra_bytes_test.go +++ b/lnwire/extra_bytes_test.go @@ -11,6 +11,12 @@ import ( "github.com/stretchr/testify/require" ) +var ( + tlvType1 tlv.TlvType1 + tlvType2 tlv.TlvType2 + tlvType3 tlv.TlvType3 +) + // TestExtraOpaqueDataEncodeDecode tests that we're able to encode/decode // arbitrary payloads. func TestExtraOpaqueDataEncodeDecode(t *testing.T) { @@ -71,10 +77,7 @@ func TestExtraOpaqueDataEncodeDecode(t *testing.T) { newTestCase.inputBytes = make([]byte, numBytes) _, err := r.Read(newTestCase.inputBytes) - if err != nil { - t.Fatalf("unable to gen random bytes: %v", err) - return - } + require.NoError(t, err) } v[0] = reflect.ValueOf(newTestCase) @@ -86,14 +89,6 @@ func TestExtraOpaqueDataEncodeDecode(t *testing.T) { } } -type recordProducer struct { - record tlv.Record -} - -func (r *recordProducer) Record() tlv.Record { - return r.record -} - // TestExtraOpaqueDataPackUnpackRecords tests that we're able to pack a set of // tlv.Records into a stream, and unpack them on the other side to obtain the // same set of records. @@ -151,3 +146,368 @@ func TestExtraOpaqueDataPackUnpackRecords(t *testing.T) { t.Fatalf("type2 not found in typeMap") } } + +// TestPackRecords tests that we're able to pack a set of records into an +// ExtraOpaqueData instance, and then extract them back out. Crucially, we'll +// ensure that records can be packed in any order, and we'll ensure that the +// unpacked records are valid. +func TestPackRecords(t *testing.T) { + t.Parallel() + + // Create an empty ExtraOpaqueData instance. + extraBytes := ExtraOpaqueData{} + + var ( + // Record type 1. + recordBytes1 = []byte("recordBytes1") + tlvRecord1 = tlv.NewPrimitiveRecord[tlv.TlvType1]( + recordBytes1, + ) + + // Record type 2. + recordBytes2 = []byte("recordBytes2") + tlvRecord2 = tlv.NewPrimitiveRecord[tlv.TlvType2]( + recordBytes2, + ) + + // Record type 3. + recordBytes3 = []byte("recordBytes3") + tlvRecord3 = tlv.NewPrimitiveRecord[tlv.TlvType3]( + recordBytes3, + ) + ) + + // Pack records 1 and 2 into the ExtraOpaqueData instance. + err := extraBytes.PackRecords( + []tlv.RecordProducer{&tlvRecord1, &tlvRecord2}..., + ) + require.NoError(t, err) + + // Examine the records that were packed into the ExtraOpaqueData. + extractedRecords, err := extraBytes.ExtractRecords() + require.NoError(t, err) + + require.Equal(t, 2, len(extractedRecords)) + require.Equal(t, recordBytes1, extractedRecords[tlvType1.TypeVal()]) + require.Equal(t, recordBytes2, extractedRecords[tlvType2.TypeVal()]) + + // Pack records 1, 2, and 3 into the ExtraOpaqueData instance. + err = extraBytes.PackRecords( + []tlv.RecordProducer{&tlvRecord3, &tlvRecord1, &tlvRecord2}..., + ) + require.NoError(t, err) + + // Examine the records that were packed into the ExtraOpaqueData. + extractedRecords, err = extraBytes.ExtractRecords() + require.NoError(t, err) + + require.Equal(t, 3, len(extractedRecords)) + require.Equal(t, recordBytes1, extractedRecords[tlvType1.TypeVal()]) + require.Equal(t, recordBytes2, extractedRecords[tlvType2.TypeVal()]) + require.Equal(t, recordBytes3, extractedRecords[tlvType3.TypeVal()]) +} + +type dummyRecordProducer struct { + typ tlv.Type + scratchValue []byte + expectedValue []byte +} + +func (d *dummyRecordProducer) Record() tlv.Record { + return tlv.MakePrimitiveRecord(d.typ, &d.scratchValue) +} + +// TestExtraOpaqueData tests that we're able to properly encode/decode an +// ExtraOpaqueData instance. +func TestExtraOpaqueData(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + types tlv.TypeMap + expectedData ExtraOpaqueData + expectedTypes tlv.TypeMap + decoders []tlv.RecordProducer + }{ + { + name: "empty map", + expectedTypes: tlv.TypeMap{}, + expectedData: make([]byte, 0), + }, + { + name: "single record", + types: tlv.TypeMap{ + tlvType1.TypeVal(): []byte{1, 2, 3}, + }, + expectedData: ExtraOpaqueData{ + 0x01, 0x03, 1, 2, 3, + }, + expectedTypes: tlv.TypeMap{ + tlvType1.TypeVal(): []byte{1, 2, 3}, + }, + decoders: []tlv.RecordProducer{ + &dummyRecordProducer{ + typ: tlvType1.TypeVal(), + expectedValue: []byte{1, 2, 3}, + }, + }, + }, + { + name: "multiple records", + types: tlv.TypeMap{ + tlvType2.TypeVal(): []byte{4, 5, 6}, + tlvType1.TypeVal(): []byte{1, 2, 3}, + }, + expectedData: ExtraOpaqueData{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + }, + expectedTypes: tlv.TypeMap{ + tlvType1.TypeVal(): []byte{1, 2, 3}, + tlvType2.TypeVal(): []byte{4, 5, 6}, + }, + decoders: []tlv.RecordProducer{ + &dummyRecordProducer{ + typ: tlvType1.TypeVal(), + expectedValue: []byte{1, 2, 3}, + }, + &dummyRecordProducer{ + typ: tlvType2.TypeVal(), + expectedValue: []byte{4, 5, 6}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // First, test the constructor. + opaqueData, err := NewExtraOpaqueData(tc.types) + require.NoError(t, err) + + require.Equal(t, tc.expectedData, opaqueData) + + // Now encode/decode. + var b bytes.Buffer + err = opaqueData.Encode(&b) + require.NoError(t, err) + + var decoded ExtraOpaqueData + err = decoded.Decode(&b) + require.NoError(t, err) + + require.Equal(t, opaqueData, decoded) + + // Now RecordProducers/PackRecords. + producers, err := opaqueData.RecordProducers() + require.NoError(t, err) + + var packed ExtraOpaqueData + err = packed.PackRecords(producers...) + require.NoError(t, err) + + // PackRecords returns nil vs. an empty slice if there + // are no records. We need to handle this case + // separately. + if len(producers) == 0 { + // Make sure the packed data is empty. + require.Empty(t, packed) + + // Now change it to an empty slice for the + // comparison below. + packed = make([]byte, 0) + } + require.Equal(t, opaqueData, packed) + + // ExtractRecords with an empty set of record producers + // should return the original type map. + extracted, err := opaqueData.ExtractRecords() + require.NoError(t, err) + + require.Equal(t, tc.expectedTypes, extracted) + + if len(tc.decoders) == 0 { + return + } + + // ExtractRecords with a set of record producers should + // only return the types that weren't in the passed-in + // set of producers. + extracted, err = opaqueData.ExtractRecords( + tc.decoders..., + ) + require.NoError(t, err) + + for parsedType := range tc.expectedTypes { + remainder, ok := extracted[parsedType] + require.True(t, ok) + require.Nil(t, remainder) + } + + for _, dec := range tc.decoders { + //nolint:forcetypeassert + dec := dec.(*dummyRecordProducer) + require.Equal( + t, dec.expectedValue, dec.scratchValue, + ) + } + }) + } +} + +// TestExtractAndMerge tests that the ParseAndExtractCustomRecords and +// MergeAndEncode functions work as expected. +func TestExtractAndMerge(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + knownRecords []tlv.RecordProducer + extraData ExtraOpaqueData + customRecords CustomRecords + expectedErr string + expectEncoded []byte + }{ + { + name: "invalid custom record", + customRecords: CustomRecords{ + 123: []byte("invalid"), + }, + expectedErr: "custom records validation error", + }, + { + name: "empty everything", + }, + { + name: "just extra data", + extraData: ExtraOpaqueData{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + }, + expectEncoded: []byte{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + }, + }, + { + name: "extra data with known record", + extraData: ExtraOpaqueData{ + 0x04, 0x03, 4, 4, 4, + 0x05, 0x03, 5, 5, 5, + }, + knownRecords: []tlv.RecordProducer{ + &dummyRecordProducer{ + typ: tlvType1.TypeVal(), + scratchValue: []byte{1, 2, 3}, + expectedValue: []byte{1, 2, 3}, + }, + &dummyRecordProducer{ + typ: tlvType2.TypeVal(), + scratchValue: []byte{4, 5, 6}, + expectedValue: []byte{4, 5, 6}, + }, + }, + expectEncoded: []byte{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + 0x04, 0x03, 4, 4, 4, + 0x05, 0x03, 5, 5, 5, + }, + }, + { + name: "extra data and custom records with known record", + extraData: ExtraOpaqueData{ + 0x04, 0x03, 4, 4, 4, + 0x05, 0x03, 5, 5, 5, + }, + customRecords: CustomRecords{ + MinCustomRecordsTlvType + 1: []byte{99, 99, 99}, + }, + knownRecords: []tlv.RecordProducer{ + &dummyRecordProducer{ + typ: tlvType1.TypeVal(), + scratchValue: []byte{1, 2, 3}, + expectedValue: []byte{1, 2, 3}, + }, + &dummyRecordProducer{ + typ: tlvType2.TypeVal(), + scratchValue: []byte{4, 5, 6}, + expectedValue: []byte{4, 5, 6}, + }, + }, + expectEncoded: []byte{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + 0x04, 0x03, 4, 4, 4, + 0x05, 0x03, 5, 5, 5, + 0xfe, 0x0, 0x1, 0x0, 0x1, 0x3, 0x63, 0x63, 0x63, + }, + }, + { + name: "duplicate records", + extraData: ExtraOpaqueData{ + 0x01, 0x03, 4, 4, 4, + 0x05, 0x03, 5, 5, 5, + }, + customRecords: CustomRecords{ + MinCustomRecordsTlvType + 1: []byte{99, 99, 99}, + }, + knownRecords: []tlv.RecordProducer{ + &dummyRecordProducer{ + typ: tlvType1.TypeVal(), + scratchValue: []byte{1, 2, 3}, + expectedValue: []byte{1, 2, 3}, + }, + &dummyRecordProducer{ + typ: tlvType2.TypeVal(), + scratchValue: []byte{4, 5, 6}, + expectedValue: []byte{4, 5, 6}, + }, + }, + expectedErr: "duplicate record type: 1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encoded, err := MergeAndEncode( + tc.knownRecords, tc.extraData, tc.customRecords, + ) + + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectEncoded, encoded) + + // Clear all the scratch values, to make sure they're + // decoded from the data again. + for _, dec := range tc.knownRecords { + //nolint:forcetypeassert + dec := dec.(*dummyRecordProducer) + dec.scratchValue = nil + } + + pCR, pKR, pED, err := ParseAndExtractCustomRecords( + encoded, tc.knownRecords..., + ) + require.NoError(t, err) + + require.Equal(t, tc.customRecords, pCR) + require.Equal(t, tc.extraData, pED) + + for _, dec := range tc.knownRecords { + //nolint:forcetypeassert + dec := dec.(*dummyRecordProducer) + require.Equal( + t, dec.expectedValue, dec.scratchValue, + ) + + require.Contains(t, pKR, dec.typ) + } + }) + } +} diff --git a/lnwire/features.go b/lnwire/features.go index 437a50dc21..c75d36b397 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/fuzz_test.go b/lnwire/fuzz_test.go index 9a759604aa..d6ec3b61b3 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -494,9 +494,47 @@ func FuzzReplyChannelRange(f *testing.F) { // Prefix with MsgReplyChannelRange. data = prefixWithMsgType(data, MsgReplyChannelRange) - // Pass the message into our general fuzz harness for wire - // messages! - harness(t, data) + // Because require.Equal considers nil slices and empty slices + // to be non-equal, we must manually compare the Timestamps + // field rather than using the harness. + + if len(data) > MaxSliceLength { + return + } + + r := bytes.NewReader(data) + msg, err := ReadMessage(r, 0) + if err != nil { + return + } + + // We will serialize the message into a new bytes buffer. + var b bytes.Buffer + _, err = WriteMessage(&b, msg, 0) + require.NoError(t, err) + + // Deserialize the message from the serialized bytes buffer, and + // then assert that the original message is equal to the newly + // deserialized message. + newMsg, err := ReadMessage(&b, 0) + require.NoError(t, err) + + require.IsType(t, &ReplyChannelRange{}, msg) + first, _ := msg.(*ReplyChannelRange) + require.IsType(t, &ReplyChannelRange{}, newMsg) + second, _ := newMsg.(*ReplyChannelRange) + + // We can't use require.Equal for Timestamps, since we consider + // the empty slice and nil to be equivalent. + require.Equal(t, len(first.Timestamps), len(second.Timestamps)) + for i, ts1 := range first.Timestamps { + ts2 := second.Timestamps[i] + require.Equal(t, ts1, ts2) + } + first.Timestamps = nil + second.Timestamps = nil + + require.Equal(t, first, second) }) } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 122a996601..0502129dc2 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -2,6 +2,7 @@ package lnwire import ( "bytes" + crand "crypto/rand" "encoding/binary" "encoding/hex" "fmt" @@ -134,6 +135,27 @@ func randPubKey() (*btcec.PublicKey, error) { return priv.PubKey(), nil } +// pubkeyFromHex parses a Bitcoin public key from a hex encoded string. +func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { + pubKeyBytes, err := hex.DecodeString(keyHex) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(pubKeyBytes) +} + +// generateRandomBytes returns a slice of n random bytes. +func generateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := crand.Read(b) + if err != nil { + return nil, err + } + + return b, nil +} + func randRawKey() ([33]byte, error) { var n [33]byte @@ -389,6 +411,37 @@ func TestEmptyMessageUnknownType(t *testing.T) { } } +// randCustomRecords generates a random set of custom records for testing. +func randCustomRecords(t *testing.T, r *rand.Rand) CustomRecords { + var ( + customRecords = CustomRecords{} + + // We'll generate a random number of records, between 1 and 10. + numRecords = r.Intn(9) + 1 + ) + + // For each record, we'll generate a random key and value. + for i := 0; i < numRecords; i++ { + // Keys must be equal to or greater than + // MinCustomRecordsTlvType. + keyOffset := uint64(r.Intn(100)) + key := MinCustomRecordsTlvType + keyOffset + + // Values are byte slices of any length. + value := make([]byte, r.Intn(10)) + _, err := r.Read(value) + require.NoError(t, err) + + customRecords[key] = value + } + + // Validate the custom records as a sanity check. + err := customRecords.Validate() + require.NoError(t, err) + + return customRecords +} + // TestLightningWireProtocol uses the testing/quick package to create a series // of fuzz tests to attempt to break a primary scenario which is implemented as // property based testing scenario. @@ -718,7 +771,6 @@ func TestLightningWireProtocol(t *testing.T) { req := Shutdown{ ChannelID: ChannelID(c), Address: shutdownAddr, - ExtraData: make([]byte, 0), } if r.Int31()%2 == 0 { @@ -880,18 +932,22 @@ func TestLightningWireProtocol(t *testing.T) { // Only create the slice if there will be any signatures // in it to prevent false positive test failures due to // an empty slice versus a nil slice. - numSigs := uint16(r.Int31n(1019)) + numSigs := uint16(r.Int31n(500)) if numSigs > 0 { req.HtlcSigs = make([]Sig, numSigs) } for i := 0; i < int(numSigs); i++ { - req.HtlcSigs[i], err = NewSigFromSignature(testSig) + req.HtlcSigs[i], err = NewSigFromSignature( + testSig, + ) if err != nil { t.Fatalf("unable to parse sig: %v", err) return } } + req.CustomRecords = randCustomRecords(t, r) + // 50/50 chance to attach a partial sig. if r.Int31()%2 == 0 { req.PartialSig = somePartialSigWithNonce(t, r) @@ -1369,6 +1425,8 @@ func TestLightningWireProtocol(t *testing.T) { _, err = r.Read(req.OnionBlob[:]) require.NoError(t, err) + req.CustomRecords = randCustomRecords(t, r) + // Generate a blinding point 50% of the time, since not // all update adds will use route blinding. if r.Int31()%2 == 0 { @@ -1387,6 +1445,29 @@ func TestLightningWireProtocol(t *testing.T) { ) } + v[0] = reflect.ValueOf(*req) + }, + MsgUpdateFulfillHTLC: func(v []reflect.Value, r *rand.Rand) { + req := &UpdateFulfillHTLC{ + ID: r.Uint64(), + } + + _, err := r.Read(req.ChanID[:]) + require.NoError(t, err) + + _, err = r.Read(req.PaymentPreimage[:]) + require.NoError(t, err) + + req.CustomRecords = randCustomRecords(t, r) + + // Generate some random TLV records 50% of the time. + if r.Int31()%2 == 0 { + req.ExtraData = []byte{ + 0x01, 0x03, 1, 2, 3, + 0x02, 0x03, 4, 5, 6, + } + } + v[0] = reflect.ValueOf(*req) }, } @@ -1619,22 +1700,28 @@ func TestLightningWireProtocol(t *testing.T) { }, } for _, test := range tests { - 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, + 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, + } } - } - 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/lnwire/onion_error.go b/lnwire/onion_error.go index 66db6a9f58..f8a8bf0698 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -10,6 +10,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/tlv" ) @@ -1271,14 +1272,19 @@ func (f *FailInvalidBlinding) Encode(w *bytes.Buffer, _ uint32) error { } // NewInvalidBlinding creates new instance of FailInvalidBlinding. -func NewInvalidBlinding(onion []byte) *FailInvalidBlinding { +func NewInvalidBlinding( + onion fn.Option[[OnionPacketSize]byte]) *FailInvalidBlinding { // The spec allows empty onion hashes for invalid blinding, so we only // include our onion hash if it's provided. - if onion == nil { + if onion.IsNone() { return &FailInvalidBlinding{} } - return &FailInvalidBlinding{OnionSHA256: sha256.Sum256(onion)} + shaSum := fn.MapOptionZ(onion, func(o [OnionPacketSize]byte) [32]byte { + return sha256.Sum256(o[:]) + }) + + return &FailInvalidBlinding{OnionSHA256: shaSum} } // DecodeFailure decodes, validates, and parses the lnwire onion failure, for diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index a06c572dc4..bc14d5d420 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -9,11 +9,12 @@ import ( "testing" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/fn" "github.com/stretchr/testify/require" ) var ( - testOnionHash = []byte{} + testOnionHash = [OnionPacketSize]byte{} testAmount = MilliSatoshi(1) testCtlvExpiry = uint32(2) testFlags = uint16(2) @@ -43,9 +44,9 @@ var onionFailures = []FailureMessage{ &FailMPPTimeout{}, NewFailIncorrectDetails(99, 100), - NewInvalidOnionVersion(testOnionHash), - NewInvalidOnionHmac(testOnionHash), - NewInvalidOnionKey(testOnionHash), + NewInvalidOnionVersion(testOnionHash[:]), + NewInvalidOnionHmac(testOnionHash[:]), + NewInvalidOnionKey(testOnionHash[:]), NewTemporaryChannelFailure(&testChannelUpdate), NewTemporaryChannelFailure(nil), NewAmountBelowMinimum(testAmount, testChannelUpdate), @@ -56,7 +57,7 @@ var onionFailures = []FailureMessage{ NewFinalIncorrectCltvExpiry(testCtlvExpiry), NewFinalIncorrectHtlcAmount(testAmount), NewInvalidOnionPayload(testType, testOffset), - NewInvalidBlinding(testOnionHash), + NewInvalidBlinding(fn.Some(testOnionHash)), } // TestEncodeDecodeCode tests the ability of onion errors to be properly encoded diff --git a/lnwire/reply_channel_range.go b/lnwire/reply_channel_range.go index ea45a5843c..591fc2bd60 100644 --- a/lnwire/reply_channel_range.go +++ b/lnwire/reply_channel_range.go @@ -104,6 +104,12 @@ func (c *ReplyChannelRange) Decode(r io.Reader, pver uint32) error { // Set the corresponding TLV types if they were included in the stream. if val, ok := typeMap[TimestampsRecordType]; ok && val == nil { c.Timestamps = timeStamps + + // Check that a timestamp was provided for each SCID. + if len(c.Timestamps) != len(c.ShortChanIDs) { + return fmt.Errorf("number of timestamps does not " + + "match number of SCIDs") + } } if len(tlvRecords) != 0 { diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index c5455651b8..b9899fcfb9 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -38,6 +38,11 @@ type Shutdown struct { // co-op sign offer. ShutdownNonce ShutdownNonceTLV + // CustomRecords maps TLV types to byte slices, storing arbitrary data + // intended for inclusion in the ExtraData field of the Shutdown + // message. + CustomRecords CustomRecords + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -56,7 +61,7 @@ func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown { // interface. var _ Message = (*Shutdown)(nil) -// Decode deserializes a serialized Shutdown stored in the passed io.Reader +// Decode deserializes a serialized Shutdown from the passed io.Reader, // observing the specified protocol version. // // This is part of the lnwire.Message interface. @@ -71,20 +76,23 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { return err } + // Extract TLV records from the extra data field. musigNonce := s.ShutdownNonce.Zero() - typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + + customRecords, parsed, extraData, err := ParseAndExtractCustomRecords( + tlvRecords, &musigNonce, + ) if err != nil { return err } - // Set the corresponding TLV types if they were included in the stream. - if val, ok := typeMap[s.ShutdownNonce.TlvType()]; ok && val == nil { + // Assign the parsed records back to the message. + if _, ok := parsed[musigNonce.TlvType()]; ok { s.ShutdownNonce = tlv.SomeRecordT(musigNonce) } - if len(tlvRecords) != 0 { - s.ExtraData = tlvRecords - } + s.CustomRecords = customRecords + s.ExtraData = extraData return nil } @@ -94,26 +102,28 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error { - recordProducers := make([]tlv.RecordProducer, 0, 1) - s.ShutdownNonce.WhenSome( - func(nonce tlv.RecordT[ShutdownNonceType, Musig2Nonce]) { - recordProducers = append(recordProducers, &nonce) - }, - ) - err := EncodeMessageExtraData(&s.ExtraData, recordProducers...) - if err != nil { + if err := WriteChannelID(w, s.ChannelID); err != nil { return err } - if err := WriteChannelID(w, s.ChannelID); err != nil { + if err := WriteDeliveryAddress(w, s.Address); err != nil { return err } - if err := WriteDeliveryAddress(w, s.Address); err != nil { + // Only include nonce in extra data if present. + var records []tlv.RecordProducer + s.ShutdownNonce.WhenSome( + func(nonce tlv.RecordT[ShutdownNonceType, Musig2Nonce]) { + records = append(records, &nonce) + }, + ) + + extraData, err := MergeAndEncode(records, s.ExtraData, s.CustomRecords) + if err != nil { return err } - return WriteBytes(w, s.ExtraData) + return WriteBytes(w, extraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/shutdown_test.go b/lnwire/shutdown_test.go new file mode 100644 index 0000000000..7275efc9fb --- /dev/null +++ b/lnwire/shutdown_test.go @@ -0,0 +1,145 @@ +package lnwire + +import ( + "bytes" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// testCaseShutdown is a test case for the Shutdown message. +type testCaseShutdown struct { + // Msg is the message to be encoded and decoded. + Msg Shutdown + + // ExpectEncodeError is a flag that indicates whether we expect the + // encoding of the message to fail. + ExpectEncodeError bool +} + +// generateShutdownTestCases generates a set of Shutdown message test cases. +func generateShutdownTestCases(t *testing.T) []testCaseShutdown { + // Firstly, we'll set basic values for the message fields. + // + // Generate random channel ID. + chanIDBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var chanID ChannelID + copy(chanID[:], chanIDBytes) + + // Generate random payment preimage. + paymentPreimageBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var paymentPreimage [32]byte + copy(paymentPreimage[:], paymentPreimageBytes) + + deliveryAddr, err := generateRandomBytes(16) + require.NoError(t, err) + + // Define custom records. + recordKey1 := uint64(MinCustomRecordsTlvType + 1) + recordValue1, err := generateRandomBytes(10) + require.NoError(t, err) + + recordKey2 := uint64(MinCustomRecordsTlvType + 2) + recordValue2, err := generateRandomBytes(10) + require.NoError(t, err) + + customRecords := CustomRecords{ + recordKey1: recordValue1, + recordKey2: recordValue2, + } + + dummyPubKey, err := pubkeyFromHex( + "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d4" + + "8236c39", + ) + require.NoError(t, err) + + muSig2Nonce, err := musig2.GenNonces(musig2.WithPublicKey(dummyPubKey)) + require.NoError(t, err) + + // Construct an instance of extra data that contains records with TLV + // types below the minimum custom records threshold and that lack + // corresponding fields in the message struct. Content should persist in + // the extra data field after encoding and decoding. + var ( + recordBytes45 = []byte("recordBytes45") + tlvRecord45 = tlv.NewPrimitiveRecord[tlv.TlvType45]( + recordBytes45, + ) + + recordBytes55 = []byte("recordBytes55") + tlvRecord55 = tlv.NewPrimitiveRecord[tlv.TlvType55]( + recordBytes55, + ) + ) + + var extraData ExtraOpaqueData + err = extraData.PackRecords( + []tlv.RecordProducer{&tlvRecord45, &tlvRecord55}..., + ) + require.NoError(t, err) + + return []testCaseShutdown{ + { + Msg: Shutdown{ + ChannelID: chanID, + CustomRecords: customRecords, + ExtraData: extraData, + Address: deliveryAddr, + }, + }, + { + Msg: Shutdown{ + ChannelID: chanID, + CustomRecords: customRecords, + ExtraData: extraData, + Address: deliveryAddr, + ShutdownNonce: SomeShutdownNonce( + muSig2Nonce.PubNonce, + ), + }, + }, + } +} + +// TestShutdownEncodeDecode tests Shutdown message encoding and decoding for all +// supported field values. +func TestShutdownEncodeDecode(t *testing.T) { + t.Parallel() + + // Generate test cases. + testCases := generateShutdownTestCases(t) + + // Execute test cases. + for tcIdx, tc := range testCases { + t.Run(fmt.Sprintf("testcase-%d", tcIdx), func(t *testing.T) { + // Encode test case message. + var buf bytes.Buffer + err := tc.Msg.Encode(&buf, 0) + + // Check if we expect an encoding error. + if tc.ExpectEncodeError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Decode the encoded message bytes message. + var actualMsg Shutdown + decodeReader := bytes.NewReader(buf.Bytes()) + err = actualMsg.Decode(decodeReader, 0) + require.NoError(t, err) + + // Compare the two messages to ensure equality. + require.Equal(t, tc.Msg, actualMsg) + }) + } +} diff --git a/lnwire/update_add_htlc.go b/lnwire/update_add_htlc.go index 8a40710e82..0a377e710f 100644 --- a/lnwire/update_add_htlc.go +++ b/lnwire/update_add_htlc.go @@ -72,6 +72,11 @@ type UpdateAddHTLC struct { // next hop for this htlc. BlindingPoint BlindingPointRecord + // CustomRecords maps TLV types to byte slices, storing arbitrary data + // intended for inclusion in the ExtraData field of the UpdateAddHTLC + // message. + CustomRecords CustomRecords + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -92,6 +97,10 @@ var _ Message = (*UpdateAddHTLC)(nil) // // This is part of the lnwire.Message interface. func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { + // msgExtraData is a temporary variable used to read the message extra + // data field from the reader. + var msgExtraData ExtraOpaqueData + if err := ReadElements(r, &c.ChanID, &c.ID, @@ -99,26 +108,28 @@ func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error { c.PaymentHash[:], &c.Expiry, c.OnionBlob[:], - &c.ExtraData, + &msgExtraData, ); err != nil { return err } + // Extract TLV records from the extra data field. blindingRecord := c.BlindingPoint.Zero() - tlvMap, err := c.ExtraData.ExtractRecords(&blindingRecord) + + customRecords, parsed, extraData, err := ParseAndExtractCustomRecords( + msgExtraData, &blindingRecord, + ) if err != nil { return err } - if val, ok := tlvMap[c.BlindingPoint.TlvType()]; ok && val == nil { + // Assign the parsed records back to the message. + if parsed.Contains(blindingRecord.TlvType()) { c.BlindingPoint = tlv.SomeRecordT(blindingRecord) } - // Set extra data to nil if we didn't parse anything out of it so that - // we can use assert.Equal in tests. - if len(tlvMap) == 0 { - c.ExtraData = nil - } + c.CustomRecords = customRecords + c.ExtraData = extraData return nil } @@ -154,19 +165,18 @@ func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error { // Only include blinding point in extra data if present. var records []tlv.RecordProducer + c.BlindingPoint.WhenSome( + func(b tlv.RecordT[BlindingPointTlvType, *btcec.PublicKey]) { + records = append(records, &b) + }, + ) - c.BlindingPoint.WhenSome(func(b tlv.RecordT[BlindingPointTlvType, - *btcec.PublicKey]) { - - records = append(records, &b) - }) - - err := EncodeMessageExtraData(&c.ExtraData, records...) + extraData, err := MergeAndEncode(records, c.ExtraData, c.CustomRecords) if err != nil { return err } - return WriteBytes(w, c.ExtraData) + return WriteBytes(w, extraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_add_htlc_test.go b/lnwire/update_add_htlc_test.go new file mode 100644 index 0000000000..53f8921bd9 --- /dev/null +++ b/lnwire/update_add_htlc_test.go @@ -0,0 +1,188 @@ +package lnwire + +import ( + "bytes" + "fmt" + "testing" + + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// testCase is a test case for the UpdateAddHTLC message. +type testCase struct { + // Msg is the message to be encoded and decoded. + Msg UpdateAddHTLC + + // ExpectEncodeError is a flag that indicates whether we expect the + // encoding of the message to fail. + ExpectEncodeError bool +} + +// generateTestCases generates a set of UpdateAddHTLC message test cases. +func generateTestCases(t *testing.T) []testCase { + // Firstly, we'll set basic values for the message fields. + // + // Generate random channel ID. + chanIDBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var chanID ChannelID + copy(chanID[:], chanIDBytes) + + // Generate random payment hash. + paymentHashBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var paymentHash [32]byte + copy(paymentHash[:], paymentHashBytes) + + // Generate random onion blob. + onionBlobBytes, err := generateRandomBytes(OnionPacketSize) + require.NoError(t, err) + + var onionBlob [OnionPacketSize]byte + copy(onionBlob[:], onionBlobBytes) + + // Define the blinding point. + blinding, err := pubkeyFromHex( + "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d4" + + "8236c39", + ) + require.NoError(t, err) + + blindingPoint := tlv.SomeRecordT( + tlv.NewPrimitiveRecord[BlindingPointTlvType](blinding), + ) + + // Define custom records. + recordKey1 := uint64(MinCustomRecordsTlvType + 1) + recordValue1, err := generateRandomBytes(10) + require.NoError(t, err) + + recordKey2 := uint64(MinCustomRecordsTlvType + 2) + recordValue2, err := generateRandomBytes(10) + require.NoError(t, err) + + customRecords := CustomRecords{ + recordKey1: recordValue1, + recordKey2: recordValue2, + } + + // Construct an instance of extra data that contains records with TLV + // types below the minimum custom records threshold and that lack + // corresponding fields in the message struct. Content should persist in + // the extra data field after encoding and decoding. + var ( + recordBytes45 = []byte("recordBytes45") + tlvRecord45 = tlv.NewPrimitiveRecord[tlv.TlvType45]( + recordBytes45, + ) + + recordBytes55 = []byte("recordBytes55") + tlvRecord55 = tlv.NewPrimitiveRecord[tlv.TlvType55]( + recordBytes55, + ) + ) + + var extraData ExtraOpaqueData + err = extraData.PackRecords( + []tlv.RecordProducer{&tlvRecord45, &tlvRecord55}..., + ) + require.NoError(t, err) + + invalidCustomRecords := CustomRecords{ + MinCustomRecordsTlvType - 1: recordValue1, + } + + return []testCase{ + { + Msg: UpdateAddHTLC{ + ChanID: chanID, + ID: 42, + Amount: MilliSatoshi(1000), + PaymentHash: paymentHash, + Expiry: 43, + OnionBlob: onionBlob, + BlindingPoint: blindingPoint, + CustomRecords: customRecords, + ExtraData: extraData, + }, + }, + // Add a test case where the blinding point field is not + // populated. + { + Msg: UpdateAddHTLC{ + ChanID: chanID, + ID: 42, + Amount: MilliSatoshi(1000), + PaymentHash: paymentHash, + Expiry: 43, + OnionBlob: onionBlob, + CustomRecords: customRecords, + }, + }, + // Add a test case where the custom records field is not + // populated. + { + Msg: UpdateAddHTLC{ + ChanID: chanID, + ID: 42, + Amount: MilliSatoshi(1000), + PaymentHash: paymentHash, + Expiry: 43, + OnionBlob: onionBlob, + BlindingPoint: blindingPoint, + }, + }, + // Add a case where the custom records are invalid. + { + Msg: UpdateAddHTLC{ + ChanID: chanID, + ID: 42, + Amount: MilliSatoshi(1000), + PaymentHash: paymentHash, + Expiry: 43, + OnionBlob: onionBlob, + BlindingPoint: blindingPoint, + CustomRecords: invalidCustomRecords, + }, + ExpectEncodeError: true, + }, + } +} + +// TestUpdateAddHtlcEncodeDecode tests UpdateAddHTLC message encoding and +// decoding for all supported field values. +func TestUpdateAddHtlcEncodeDecode(t *testing.T) { + t.Parallel() + + // Generate test cases. + testCases := generateTestCases(t) + + // Execute test cases. + for tcIdx, tc := range testCases { + t.Run(fmt.Sprintf("testcase-%d", tcIdx), func(t *testing.T) { + // Encode test case message. + var buf bytes.Buffer + err := tc.Msg.Encode(&buf, 0) + + // Check if we expect an encoding error. + if tc.ExpectEncodeError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Decode the encoded message bytes message. + var actualMsg UpdateAddHTLC + decodeReader := bytes.NewReader(buf.Bytes()) + err = actualMsg.Decode(decodeReader, 0) + require.NoError(t, err) + + // Compare the two messages to ensure equality. + require.Equal(t, tc.Msg, actualMsg) + }) + } +} diff --git a/lnwire/update_fulfill_htlc.go b/lnwire/update_fulfill_htlc.go index 275a37c87c..35aaa2ff54 100644 --- a/lnwire/update_fulfill_htlc.go +++ b/lnwire/update_fulfill_htlc.go @@ -23,6 +23,10 @@ type UpdateFulfillHTLC struct { // HTLC. PaymentPreimage [32]byte + // CustomRecords maps TLV types to byte slices, storing arbitrary data + // intended for inclusion in the ExtraData field. + CustomRecords CustomRecords + // ExtraData is the set of data that was appended to this message to // fill out the full maximum transport message size. These fields can // be used to specify optional data such as custom TLV fields. @@ -49,12 +53,31 @@ var _ Message = (*UpdateFulfillHTLC)(nil) // // This is part of the lnwire.Message interface. func (c *UpdateFulfillHTLC) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + // msgExtraData is a temporary variable used to read the message extra + // data field from the reader. + var msgExtraData ExtraOpaqueData + + if err := ReadElements(r, &c.ChanID, &c.ID, c.PaymentPreimage[:], - &c.ExtraData, + &msgExtraData, + ); err != nil { + return err + } + + // Extract custom records from the extra data field. + customRecords, _, extraData, err := ParseAndExtractCustomRecords( + msgExtraData, ) + if err != nil { + return err + } + + c.CustomRecords = customRecords + c.ExtraData = extraData + + return nil } // Encode serializes the target UpdateFulfillHTLC into the passed io.Writer @@ -74,7 +97,14 @@ func (c *UpdateFulfillHTLC) Encode(w *bytes.Buffer, pver uint32) error { return err } - return WriteBytes(w, c.ExtraData) + // Combine the custom records and the extra data, then encode the + // result as a byte slice. + extraData, err := MergeAndEncode(nil, c.ExtraData, c.CustomRecords) + if err != nil { + return err + } + + return WriteBytes(w, extraData) } // MsgType returns the integer uniquely identifying this message type on the diff --git a/lnwire/update_fulfill_htlc_test.go b/lnwire/update_fulfill_htlc_test.go new file mode 100644 index 0000000000..e38b3a9b82 --- /dev/null +++ b/lnwire/update_fulfill_htlc_test.go @@ -0,0 +1,129 @@ +package lnwire + +import ( + "bytes" + "fmt" + "testing" + + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// testCaseUpdateFulfill is a test case for the UpdateFulfillHTLC message. +type testCaseUpdateFulfill struct { + // Msg is the message to be encoded and decoded. + Msg UpdateFulfillHTLC + + // ExpectEncodeError is a flag that indicates whether we expect the + // encoding of the message to fail. + ExpectEncodeError bool +} + +// generateTestCases generates a set of UpdateFulfillHTLC message test cases. +func generateUpdateFulfillTestCases(t *testing.T) []testCaseUpdateFulfill { + // Firstly, we'll set basic values for the message fields. + // + // Generate random channel ID. + chanIDBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var chanID ChannelID + copy(chanID[:], chanIDBytes) + + // Generate random payment preimage. + paymentPreimageBytes, err := generateRandomBytes(32) + require.NoError(t, err) + + var paymentPreimage [32]byte + copy(paymentPreimage[:], paymentPreimageBytes) + + // Define custom records. + recordKey1 := uint64(MinCustomRecordsTlvType + 1) + recordValue1, err := generateRandomBytes(10) + require.NoError(t, err) + + recordKey2 := uint64(MinCustomRecordsTlvType + 2) + recordValue2, err := generateRandomBytes(10) + require.NoError(t, err) + + customRecords := CustomRecords{ + recordKey1: recordValue1, + recordKey2: recordValue2, + } + + // Construct an instance of extra data that contains records with TLV + // types below the minimum custom records threshold and that lack + // corresponding fields in the message struct. Content should persist in + // the extra data field after encoding and decoding. + var ( + recordBytes45 = []byte("recordBytes45") + tlvRecord45 = tlv.NewPrimitiveRecord[tlv.TlvType45]( + recordBytes45, + ) + + recordBytes55 = []byte("recordBytes55") + tlvRecord55 = tlv.NewPrimitiveRecord[tlv.TlvType55]( + recordBytes55, + ) + ) + + var extraData ExtraOpaqueData + err = extraData.PackRecords( + []tlv.RecordProducer{&tlvRecord45, &tlvRecord55}..., + ) + require.NoError(t, err) + + return []testCaseUpdateFulfill{ + { + Msg: UpdateFulfillHTLC{ + ChanID: chanID, + ID: 42, + PaymentPreimage: paymentPreimage, + }, + }, + { + Msg: UpdateFulfillHTLC{ + ChanID: chanID, + ID: 42, + PaymentPreimage: paymentPreimage, + CustomRecords: customRecords, + ExtraData: extraData, + }, + }, + } +} + +// TestUpdateFulfillHtlcEncodeDecode tests UpdateFulfillHTLC message encoding +// and decoding for all supported field values. +func TestUpdateFulfillHtlcEncodeDecode(t *testing.T) { + t.Parallel() + + // Generate test cases. + testCases := generateUpdateFulfillTestCases(t) + + // Execute test cases. + for tcIdx, tc := range testCases { + t.Run(fmt.Sprintf("testcase-%d", tcIdx), func(t *testing.T) { + // Encode test case message. + var buf bytes.Buffer + err := tc.Msg.Encode(&buf, 0) + + // Check if we expect an encoding error. + if tc.ExpectEncodeError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Decode the encoded message bytes message. + var actualMsg UpdateFulfillHTLC + decodeReader := bytes.NewReader(buf.Bytes()) + err = actualMsg.Decode(decodeReader, 0) + require.NoError(t, err) + + // Compare the two messages to ensure equality. + require.Equal(t, tc.Msg, actualMsg) + }) + } +} diff --git a/macaroons/fuzz_test.go b/macaroons/fuzz_test.go new file mode 100644 index 0000000000..defae4143c --- /dev/null +++ b/macaroons/fuzz_test.go @@ -0,0 +1,51 @@ +package macaroons + +import ( + "context" + "testing" + + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +func FuzzUnmarshalMacaroon(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + mac := &macaroon.Macaroon{} + _ = mac.UnmarshalBinary(data) + }) +} + +func FuzzAuthChecker(f *testing.F) { + rootKeyStore := bakery.NewMemRootKeyStore() + ctx := context.Background() + + f.Fuzz(func(t *testing.T, location, entity, action, method string, + rootKey, id []byte) { + + macService, err := NewService( + rootKeyStore, location, true, IPLockChecker, + ) + if err != nil { + return + } + + requiredPermissions := []bakery.Op{{ + Entity: entity, + Action: action, + }} + + mac, err := macaroon.New(rootKey, id, location, macaroon.V2) + if err != nil { + return + } + + macBytes, err := mac.MarshalBinary() + if err != nil { + return + } + + _ = macService.CheckMacAuth( + ctx, macBytes, requiredPermissions, method, + ) + }) +} diff --git a/make/builder.Dockerfile b/make/builder.Dockerfile index db227983a1..86762006d4 100644 --- a/make/builder.Dockerfile +++ b/make/builder.Dockerfile @@ -3,7 +3,7 @@ # /dev.Dockerfile # /.github/workflows/main.yml # /.github/workflows/release.yml -FROM golang:1.22.5-bookworm +FROM golang:1.22.6-bookworm MAINTAINER Olaoluwa Osuntokun diff --git a/msgmux/log.go b/msgmux/log.go new file mode 100644 index 0000000000..63da4791f9 --- /dev/null +++ b/msgmux/log.go @@ -0,0 +1,32 @@ +package msgmux + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "MSGX" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/msgmux/msg_router.go b/msgmux/msg_router.go new file mode 100644 index 0000000000..db9e783990 --- /dev/null +++ b/msgmux/msg_router.go @@ -0,0 +1,269 @@ +package msgmux + +import ( + "fmt" + "maps" + "sync" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // ErrDuplicateEndpoint is returned when an endpoint is registered with + // a name that already exists. + ErrDuplicateEndpoint = fmt.Errorf("endpoint already registered") + + // ErrUnableToRouteMsg is returned when a message is unable to be + // routed to any endpoints. + ErrUnableToRouteMsg = fmt.Errorf("unable to route message") +) + +// EndpointName is the name of a given endpoint. This MUST be unique across all +// registered endpoints. +type EndpointName = string + +// PeerMsg is a wire message that includes the public key of the peer that sent +// it. +type PeerMsg struct { + lnwire.Message + + // PeerPub is the public key of the peer that sent this message. + PeerPub btcec.PublicKey +} + +// Endpoint is an interface that represents a message endpoint, or the +// sub-system that will handle processing an incoming wire message. +type Endpoint interface { + // Name returns the name of this endpoint. This MUST be unique across + // all registered endpoints. + Name() EndpointName + + // CanHandle returns true if the target message can be routed to this + // endpoint. + CanHandle(msg PeerMsg) bool + + // SendMessage handles the target message, and returns true if the + // message was able being processed. + SendMessage(msg PeerMsg) bool +} + +// MsgRouter is an interface that represents a message router, which is generic +// sub-system capable of routing any incoming wire message to a set of +// registered endpoints. +type Router interface { + // RegisterEndpoint registers a new endpoint with the router. If a + // duplicate endpoint exists, an error is returned. + RegisterEndpoint(Endpoint) error + + // UnregisterEndpoint unregisters the target endpoint from the router. + UnregisterEndpoint(EndpointName) error + + // RouteMsg attempts to route the target message to a registered + // endpoint. If ANY endpoint could handle the message, then nil is + // returned. Otherwise, ErrUnableToRouteMsg is returned. + RouteMsg(PeerMsg) error + + // Start starts the peer message router. + Start() + + // Stop stops the peer message router. + Stop() +} + +// sendQuery sends a query to the main event loop, and returns the response. +func sendQuery[Q any, R any](sendChan chan fn.Req[Q, R], queryArg Q, + quit chan struct{}) fn.Result[R] { + + query, respChan := fn.NewReq[Q, R](queryArg) + + if !fn.SendOrQuit(sendChan, query, quit) { + return fn.Errf[R]("router shutting down") + } + + return fn.NewResult(fn.RecvResp(respChan, nil, quit)) +} + +// sendQueryErr is a helper function based on sendQuery that can be used when +// the query only needs an error response. +func sendQueryErr[Q any](sendChan chan fn.Req[Q, error], queryArg Q, + quitChan chan struct{}) error { + + return fn.ElimEither( + fn.Iden, fn.Iden, + sendQuery(sendChan, queryArg, quitChan).Either, + ) +} + +// EndpointsMap is a map of all registered endpoints. +type EndpointsMap map[EndpointName]Endpoint + +// MultiMsgRouter is a type of message router that is capable of routing new +// incoming messages, permitting a message to be routed to multiple registered +// endpoints. +type MultiMsgRouter struct { + startOnce sync.Once + stopOnce sync.Once + + // registerChan is the channel that all new endpoints will be sent to. + registerChan chan fn.Req[Endpoint, error] + + // unregisterChan is the channel that all endpoints that are to be + // removed are sent to. + unregisterChan chan fn.Req[EndpointName, error] + + // msgChan is the channel that all messages will be sent to for + // processing. + msgChan chan fn.Req[PeerMsg, error] + + // endpointsQueries is a channel that all queries to the endpoints map + // will be sent to. + endpointQueries chan fn.Req[Endpoint, EndpointsMap] + + wg sync.WaitGroup + quit chan struct{} +} + +// NewMultiMsgRouter creates a new instance of a peer message router. +func NewMultiMsgRouter() *MultiMsgRouter { + return &MultiMsgRouter{ + registerChan: make(chan fn.Req[Endpoint, error]), + unregisterChan: make(chan fn.Req[EndpointName, error]), + msgChan: make(chan fn.Req[PeerMsg, error]), + endpointQueries: make(chan fn.Req[Endpoint, EndpointsMap]), + quit: make(chan struct{}), + } +} + +// Start starts the peer message router. +func (p *MultiMsgRouter) Start() { + log.Infof("Starting Router") + + p.startOnce.Do(func() { + p.wg.Add(1) + go p.msgRouter() + }) +} + +// Stop stops the peer message router. +func (p *MultiMsgRouter) Stop() { + log.Infof("Stopping Router") + + p.stopOnce.Do(func() { + close(p.quit) + p.wg.Wait() + }) +} + +// RegisterEndpoint registers a new endpoint with the router. If a duplicate +// endpoint exists, an error is returned. +func (p *MultiMsgRouter) RegisterEndpoint(endpoint Endpoint) error { + return sendQueryErr(p.registerChan, endpoint, p.quit) +} + +// UnregisterEndpoint unregisters the target endpoint from the router. +func (p *MultiMsgRouter) UnregisterEndpoint(name EndpointName) error { + return sendQueryErr(p.unregisterChan, name, p.quit) +} + +// RouteMsg attempts to route the target message to a registered endpoint. If +// ANY endpoint could handle the message, then nil is returned. +func (p *MultiMsgRouter) RouteMsg(msg PeerMsg) error { + return sendQueryErr(p.msgChan, msg, p.quit) +} + +// Endpoints returns a list of all registered endpoints. +func (p *MultiMsgRouter) endpoints() fn.Result[EndpointsMap] { + return sendQuery(p.endpointQueries, nil, p.quit) +} + +// msgRouter is the main goroutine that handles all incoming messages. +func (p *MultiMsgRouter) msgRouter() { + defer p.wg.Done() + + // endpoints is a map of all registered endpoints. + endpoints := make(map[EndpointName]Endpoint) + + for { + select { + // A new endpoint was just sent in, so we'll add it to our set + // of registered endpoints. + case newEndpointMsg := <-p.registerChan: + endpoint := newEndpointMsg.Request + + log.Infof("MsgRouter: registering new "+ + "Endpoint(%s)", endpoint.Name()) + + // If this endpoint already exists, then we'll return + // an error as we require unique names. + if _, ok := endpoints[endpoint.Name()]; ok { + log.Errorf("MsgRouter: rejecting "+ + "duplicate endpoint: %v", + endpoint.Name()) + + newEndpointMsg.Resolve(ErrDuplicateEndpoint) + + continue + } + + endpoints[endpoint.Name()] = endpoint + + newEndpointMsg.Resolve(nil) + + // A request to unregister an endpoint was just sent in, so + // we'll attempt to remove it. + case endpointName := <-p.unregisterChan: + delete(endpoints, endpointName.Request) + + log.Infof("MsgRouter: unregistering "+ + "Endpoint(%s)", endpointName.Request) + + endpointName.Resolve(nil) + + // A new message was just sent in. We'll attempt to route it to + // all the endpoints that can handle it. + case msgQuery := <-p.msgChan: + msg := msgQuery.Request + + // Loop through all the endpoints and send the message + // to those that can handle it the message. + var couldSend bool + for _, endpoint := range endpoints { + if endpoint.CanHandle(msg) { + log.Tracef("MsgRouter: sending "+ + "msg %T to endpoint %s", msg, + endpoint.Name()) + + sent := endpoint.SendMessage(msg) + couldSend = couldSend || sent + } + } + + var err error + if !couldSend { + log.Tracef("MsgRouter: unable to route "+ + "msg %T", msg) + + err = ErrUnableToRouteMsg + } + + msgQuery.Resolve(err) + + // A query for the endpoint state just came in, we'll send back + // a copy of our current state. + case endpointQuery := <-p.endpointQueries: + endpointsCopy := make(EndpointsMap, len(endpoints)) + maps.Copy(endpointsCopy, endpoints) + + endpointQuery.Resolve(endpointsCopy) + + case <-p.quit: + return + } + } +} + +// A compile time check to ensure MultiMsgRouter implements the MsgRouter +// interface. +var _ Router = (*MultiMsgRouter)(nil) diff --git a/msgmux/msg_router_test.go b/msgmux/msg_router_test.go new file mode 100644 index 0000000000..af8cdedef6 --- /dev/null +++ b/msgmux/msg_router_test.go @@ -0,0 +1,157 @@ +package msgmux + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +type mockEndpoint struct { + mock.Mock +} + +func (m *mockEndpoint) Name() string { + args := m.Called() + + return args.String(0) +} + +func (m *mockEndpoint) CanHandle(msg PeerMsg) bool { + args := m.Called(msg) + + return args.Bool(0) +} + +func (m *mockEndpoint) SendMessage(msg PeerMsg) bool { + args := m.Called(msg) + + return args.Bool(0) +} + +// TestMessageRouterOperation tests the basic operation of the message router: +// add new endpoints, route to them, remove, them, etc. +func TestMessageRouterOperation(t *testing.T) { + msgRouter := NewMultiMsgRouter() + msgRouter.Start() + defer msgRouter.Stop() + + openChanMsg := PeerMsg{ + Message: &lnwire.OpenChannel{}, + } + commitSigMsg := PeerMsg{ + Message: &lnwire.CommitSig{}, + } + + errorMsg := PeerMsg{ + Message: &lnwire.Error{}, + } + + // For this test, we'll have two endpoints, each with distinct names. + // One endpoint will only handle OpenChannel, while the other will + // handle the CommitSig message. + fundingEndpoint := &mockEndpoint{} + fundingEndpointName := "funding" + fundingEndpoint.On("Name").Return(fundingEndpointName) + fundingEndpoint.On("CanHandle", openChanMsg).Return(true) + fundingEndpoint.On("CanHandle", errorMsg).Return(false) + fundingEndpoint.On("CanHandle", commitSigMsg).Return(false) + fundingEndpoint.On("SendMessage", openChanMsg).Return(true) + + commitEndpoint := &mockEndpoint{} + commitEndpointName := "commit" + commitEndpoint.On("Name").Return(commitEndpointName) + commitEndpoint.On("CanHandle", commitSigMsg).Return(true) + commitEndpoint.On("CanHandle", openChanMsg).Return(false) + commitEndpoint.On("CanHandle", errorMsg).Return(false) + commitEndpoint.On("SendMessage", commitSigMsg).Return(true) + + t.Run("add endpoints", func(t *testing.T) { + // First, we'll add the funding endpoint to the router. + require.NoError(t, msgRouter.RegisterEndpoint(fundingEndpoint)) + + endpoints, err := msgRouter.endpoints().Unpack() + require.NoError(t, err) + + // There should be a single endpoint registered. + require.Len(t, endpoints, 1) + + // The name of the registered endpoint should be "funding". + require.Equal( + t, "funding", endpoints[fundingEndpointName].Name(), + ) + }) + + t.Run("duplicate endpoint reject", func(t *testing.T) { + // Next, we'll attempt to add the funding endpoint again. This + // should return an ErrDuplicateEndpoint error. + require.ErrorIs( + t, msgRouter.RegisterEndpoint(fundingEndpoint), + ErrDuplicateEndpoint, + ) + }) + + t.Run("route to endpoint", func(t *testing.T) { + // Next, we'll add our other endpoint, then attempt to route a + // message. + require.NoError(t, msgRouter.RegisterEndpoint(commitEndpoint)) + + // If we try to route a message none of the endpoints know of, + // we should get an error. + require.ErrorIs( + t, msgRouter.RouteMsg(errorMsg), ErrUnableToRouteMsg, + ) + + fundingEndpoint.AssertCalled(t, "CanHandle", errorMsg) + commitEndpoint.AssertCalled(t, "CanHandle", errorMsg) + + // Next, we'll route the open channel message. Only the + // fundingEndpoint should be used. + require.NoError(t, msgRouter.RouteMsg(openChanMsg)) + + fundingEndpoint.AssertCalled(t, "CanHandle", openChanMsg) + commitEndpoint.AssertCalled(t, "CanHandle", openChanMsg) + + fundingEndpoint.AssertCalled(t, "SendMessage", openChanMsg) + commitEndpoint.AssertNotCalled(t, "SendMessage", openChanMsg) + + // We'll do the same for the commit sig message. + require.NoError(t, msgRouter.RouteMsg(commitSigMsg)) + + fundingEndpoint.AssertCalled(t, "CanHandle", commitSigMsg) + commitEndpoint.AssertCalled(t, "CanHandle", commitSigMsg) + + commitEndpoint.AssertCalled(t, "SendMessage", commitSigMsg) + fundingEndpoint.AssertNotCalled(t, "SendMessage", commitSigMsg) + }) + + t.Run("remove endpoints", func(t *testing.T) { + // Finally, we'll remove both endpoints. + require.NoError( + t, msgRouter.UnregisterEndpoint(fundingEndpointName), + ) + require.NoError( + t, msgRouter.UnregisterEndpoint(commitEndpointName), + ) + + endpoints, err := msgRouter.endpoints().Unpack() + require.NoError(t, err) + + // There should be no endpoints registered. + require.Len(t, endpoints, 0) + + // Trying to route a message should fail. + require.ErrorIs( + t, msgRouter.RouteMsg(openChanMsg), + ErrUnableToRouteMsg, + ) + require.ErrorIs( + t, msgRouter.RouteMsg(commitSigMsg), + ErrUnableToRouteMsg, + ) + }) + + commitEndpoint.AssertExpectations(t) + fundingEndpoint.AssertExpectations(t) +} diff --git a/peer/brontide.go b/peer/brontide.go index 920a1bcca8..fa42f13584 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -35,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" @@ -42,6 +43,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chancloser" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/msgmux" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/pool" "github.com/lightningnetwork/lnd/queue" @@ -142,6 +144,21 @@ type PendingUpdate struct { type ChannelCloseUpdate struct { ClosingTxid []byte Success bool + + // LocalCloseOutput is an optional, additional output on the closing + // transaction that the local party should be paid to. This will only be + // populated if the local balance isn't dust. + LocalCloseOutput fn.Option[chancloser.CloseOutput] + + // RemoteCloseOutput is an optional, additional output on the closing + // transaction that the remote party should be paid to. This will only + // be populated if the remote balance isn't dust. + RemoteCloseOutput fn.Option[chancloser.CloseOutput] + + // AuxOutputs is an optional set of additional outputs that might be + // included in the closing transaction. These are used for custom + // channel types. + AuxOutputs fn.Option[chancloser.AuxCloseOutputs] } // TimestampedError is a timestamped error that is used to store the most recent @@ -369,7 +386,19 @@ type Config struct { // AddLocalAlias persists an alias to an underlying alias store. AddLocalAlias func(alias, base lnwire.ShortChannelID, - gossip bool) error + gossip, liveUpdate bool) error + + // AuxLeafStore is an optional store that can be used to store auxiliary + // leaves for certain custom channel types. + AuxLeafStore fn.Option[lnwallet.AuxLeafStore] + + // 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] // 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 @@ -386,6 +415,15 @@ type Config struct { // This value will be passed to created links. MaxFeeExposure lnwire.MilliSatoshi + // MsgRouter is an optional instance of the main message router that + // the peer will use. If None, then a new default version will be used + // in place. + MsgRouter fn.Option[msgmux.Router] + + // AuxChanCloser is an optional instance of an abstraction that can be + // used to modify the way the co-op close transaction is constructed. + AuxChanCloser fn.Option[chancloser.AuxChanCloser] + // Quit is the server's quit channel. If this is closed, we halt operation. Quit chan struct{} } @@ -522,6 +560,15 @@ type Brontide struct { // potentially holding lots of un-consumed events. channelEventClient *subscribe.Client + // msgRouter is an instance of the msgmux.Router which is used to send + // off new wire messages for handing. + msgRouter fn.Option[msgmux.Router] + + // globalMsgRouter is a flag that indicates whether we have a global + // msg router. If so, then we don't worry about stopping the msg router + // when a peer disconnects. + globalMsgRouter bool + startReady chan struct{} quit chan struct{} wg sync.WaitGroup @@ -537,6 +584,17 @@ var _ lnpeer.Peer = (*Brontide)(nil) func NewBrontide(cfg Config) *Brontide { logPrefix := fmt.Sprintf("Peer(%x):", cfg.PubKeyBytes) + // We have a global message router if one was passed in via the config. + // In this case, we don't need to attempt to tear it down when the peer + // is stopped. + globalMsgRouter := cfg.MsgRouter.IsSome() + + // We'll either use the msg router instance passed in, or create a new + // blank instance. + msgRouter := cfg.MsgRouter.Alt(fn.Some[msgmux.Router]( + msgmux.NewMultiMsgRouter(), + )) + p := &Brontide{ cfg: cfg, activeSignal: make(chan struct{}), @@ -559,6 +617,8 @@ func NewBrontide(cfg Config) *Brontide { startReady: make(chan struct{}), quit: make(chan struct{}), log: build.NewPrefixLog(logPrefix, peerLog), + msgRouter: msgRouter, + globalMsgRouter: globalMsgRouter, } if cfg.Conn != nil && cfg.Conn.RemoteAddr() != nil { @@ -738,6 +798,12 @@ func (p *Brontide) Start() error { return err } + // Register the message router now as we may need to register some + // endpoints while loading the channels below. + p.msgRouter.WhenSome(func(router msgmux.Router) { + router.Start() + }) + msgs, err := p.loadActiveChannels(activeChans) if err != nil { return fmt.Errorf("unable to load channels: %w", err) @@ -839,6 +905,33 @@ func (p *Brontide) QuitSignal() <-chan struct{} { return p.quit } +// 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) (*chancloser.DeliveryAddrWithKey, error) { + + // 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. + internalKeyDesc, err := lnwallet.InternalKeyForAddr( + p.cfg.Wallet, &p.cfg.Wallet.Cfg.NetParams, deliveryScript, + ) + if err != nil { + return nil, fmt.Errorf("unable to fetch internal key: %w", err) + } + + return &chancloser.DeliveryAddrWithKey{ + DeliveryAddress: deliveryScript, + InternalKey: fn.MapOption( + func(desc keychain.KeyDescriptor) btcec.PublicKey { + return *desc.PubKey + }, + )(internalKeyDesc), + }, nil +} + // loadActiveChannels creates indexes within the peer for tracking all active // channels returned by the database. It returns a slice of channel reestablish // messages that should be sent to the peer immediately, in case we have borked @@ -874,6 +967,7 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( err = p.cfg.AddLocalAlias( aliasScid, dbChan.ShortChanID(), false, + false, ) if err != nil { return nil, err @@ -909,11 +1003,27 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( } } + var chanOpts []lnwallet.ChannelOpt + p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + 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, + p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts..., ) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create channel "+ + "state machine: %w", err) } chanPoint := dbChan.FundingOutpoint @@ -1070,9 +1180,16 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) ( return } + addr, err := p.addrWithInternalKey( + info.DeliveryScript.Val, + ) + if err != nil { + shutdownInfoErr = fmt.Errorf("unable to make "+ + "delivery addr: %w", err) + return + } chanCloser, err := p.createChanCloser( - lnChan, info.DeliveryScript.Val, feePerKw, nil, - info.Closer(), + lnChan, addr, feePerKw, nil, info.Closer(), ) if err != nil { shutdownInfoErr = fmt.Errorf("unable to "+ @@ -1368,6 +1485,14 @@ func (p *Brontide) Disconnect(reason error) { p.cfg.Conn.Close() close(p.quit) + + // If our msg router isn't global (local to this instance), then we'll + // stop it. Otherwise, we'll leave it running. + if !p.globalMsgRouter { + p.msgRouter.WhenSome(func(router msgmux.Router) { + router.Stop() + }) + } } // String returns the string representation of this peer. @@ -1809,6 +1934,22 @@ out: } } + // If a message router is active, then we'll try to have it + // handle this message. If it can, then we're able to skip the + // rest of the message handling logic. + err = fn.MapOptionZ(p.msgRouter, func(r msgmux.Router) error { + return r.RouteMsg(msgmux.PeerMsg{ + PeerPub: *p.IdentityKey(), + Message: nextMsg, + }) + }) + + // No error occurred, and the message was handled by the + // router. + if err == nil { + continue + } + var ( targetChan lnwire.ChannelID isLinkUpdate bool @@ -2125,17 +2266,18 @@ func messageSummary(msg lnwire.Message) string { ) return fmt.Sprintf("chan_id=%v, id=%v, amt=%v, expiry=%v, "+ - "hash=%x, blinding_point=%x", msg.ChanID, msg.ID, - msg.Amount, msg.Expiry, msg.PaymentHash[:], - blindingPoint) + "hash=%x, blinding_point=%x, custom_records=%v", + msg.ChanID, msg.ID, msg.Amount, msg.Expiry, + msg.PaymentHash[:], blindingPoint, msg.CustomRecords) case *lnwire.UpdateFailHTLC: return fmt.Sprintf("chan_id=%v, id=%v, reason=%x", msg.ChanID, msg.ID, msg.Reason) case *lnwire.UpdateFulfillHTLC: - return fmt.Sprintf("chan_id=%v, id=%v, pre_image=%x", - msg.ChanID, msg.ID, msg.PaymentPreimage[:]) + return fmt.Sprintf("chan_id=%v, id=%v, pre_image=%x, "+ + "custom_records=%v", msg.ChanID, msg.ID, + msg.PaymentPreimage[:], msg.CustomRecords) case *lnwire.CommitSig: return fmt.Sprintf("chan_id=%v, num_htlcs=%v", msg.ChanID, @@ -2806,8 +2948,12 @@ func (p *Brontide) fetchActiveChanCloser(chanID lnwire.ChannelID) ( return nil, fmt.Errorf("unable to estimate fee") } + addr, err := p.addrWithInternalKey(deliveryScript) + if err != nil { + return nil, fmt.Errorf("unable to parse addr: %w", err) + } chanCloser, err = p.createChanCloser( - channel, deliveryScript, feePerKw, nil, lntypes.Remote, + channel, addr, feePerKw, nil, lntypes.Remote, ) if err != nil { p.log.Errorf("unable to create chan closer: %v", err) @@ -3049,8 +3195,12 @@ func (p *Brontide) restartCoopClose(lnChan *lnwallet.LightningChannel) ( closingParty = lntypes.Local } + addr, err := p.addrWithInternalKey(deliveryScript) + if err != nil { + return nil, fmt.Errorf("unable to parse addr: %w", err) + } chanCloser, err := p.createChanCloser( - lnChan, deliveryScript, feePerKw, nil, closingParty, + lnChan, addr, feePerKw, nil, closingParty, ) if err != nil { p.log.Errorf("unable to create chan closer: %v", err) @@ -3077,8 +3227,8 @@ 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 lnwire.DeliveryAddress, fee chainfee.SatPerKWeight, - req *htlcswitch.ChanClose, + deliveryScript *chancloser.DeliveryAddrWithKey, + fee chainfee.SatPerKWeight, req *htlcswitch.ChanClose, closer lntypes.ChannelParty) (*chancloser.ChanCloser, error) { _, startingHeight, err := p.cfg.ChainIO.GetBestBlock() @@ -3099,6 +3249,7 @@ func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, MusigSession: NewMusigChanCloser(channel), FeeEstimator: &chancloser.SimpleCoopFeeEstimator{}, BroadcastTx: p.cfg.Wallet.PublishTransaction, + AuxCloser: p.cfg.AuxChanCloser, DisableChannel: func(op wire.OutPoint) error { return p.cfg.ChanStatusMgr.RequestDisable( op, false, @@ -3111,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, @@ -3170,10 +3321,17 @@ func (p *Brontide) handleLocalCloseReq(req *htlcswitch.ChanClose) { return } } + addr, err := p.addrWithInternalKey(deliveryScript) + if err != nil { + err = fmt.Errorf("unable to parse addr for channel "+ + "%v: %w", req.ChanPoint, err) + p.log.Errorf(err.Error()) + req.Err <- err + return + } chanCloser, err := p.createChanCloser( - channel, deliveryScript, req.TargetFeePerKw, req, - lntypes.Local, + channel, addr, req.TargetFeePerKw, req, lntypes.Local, ) if err != nil { p.log.Errorf(err.Error()) @@ -3395,17 +3553,25 @@ func (p *Brontide) finalizeChanClosure(chanCloser *chancloser.ChanCloser) { } } - go WaitForChanToClose(chanCloser.NegotiationHeight(), notifier, errChan, + localOut := chanCloser.LocalCloseOutput() + remoteOut := chanCloser.RemoteCloseOutput() + auxOut := chanCloser.AuxOutputs() + go WaitForChanToClose( + chanCloser.NegotiationHeight(), notifier, errChan, &chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which requested the // channel closure. if closeReq != nil { closeReq.Updates <- &ChannelCloseUpdate{ - ClosingTxid: closingTxid[:], - Success: true, + ClosingTxid: closingTxid[:], + Success: true, + LocalCloseOutput: localOut, + RemoteCloseOutput: remoteOut, + AuxOutputs: auxOut, } } - }) + }, + ) } // WaitForChanToClose uses the passed notifier to wait until the channel has @@ -4092,6 +4258,16 @@ func (p *Brontide) addActiveChannel(c *lnpeer.NewChannel) error { chanOpts = append(chanOpts, lnwallet.WithSkipNonceInit()) } + p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) { + chanOpts = append(chanOpts, lnwallet.WithLeafStore(s)) + }) + 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 // ID. diff --git a/peer/musig_chan_closer.go b/peer/musig_chan_closer.go index 6b05b1e62e..6f69a8c5b8 100644 --- a/peer/musig_chan_closer.go +++ b/peer/musig_chan_closer.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chancloser" @@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() ( } localKey, remoteKey := m.channel.MultiSigKeys() + + tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)( + m.channel.State().TapscriptRoot, + ) + m.musigSession = lnwallet.NewPartialMusigSession( *m.remoteNonce, localKey, remoteKey, m.channel.Signer, m.channel.FundingTxOut(), - lnwallet.RemoteMusigCommit, + lnwallet.RemoteMusigCommit, tapscriptTweak, ) err := m.musigSession.FinalizeSession(*m.localNonce) diff --git a/peer/test_utils.go b/peer/test_utils.go index e0ae29be8b..cce8055bdc 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -304,6 +304,10 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a, alicePool := lnwallet.NewSigPool(1, aliceSigner) channelAlice, err := lnwallet.NewLightningChannel( aliceSigner, aliceChannelState, alicePool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(lnwallet.NewAuxSignerMock( + lnwallet.EmptyMockJobHandler, + )), ) if err != nil { return nil, err @@ -316,6 +320,10 @@ func createTestPeerWithChannel(t *testing.T, updateChan func(a, bobPool := lnwallet.NewSigPool(1, bobSigner) channelBob, err := lnwallet.NewLightningChannel( bobSigner, bobChannelState, bobPool, + lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}), + lnwallet.WithAuxSigner(lnwallet.NewAuxSignerMock( + lnwallet.EmptyMockJobHandler, + )), ) if err != nil { return nil, err diff --git a/routing/bandwidth.go b/routing/bandwidth.go index 0868255685..3b80dadc7c 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -1,10 +1,14 @@ package routing import ( + "fmt" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) // bandwidthHints provides hints about the currently available balance in our @@ -19,6 +23,43 @@ type bandwidthHints interface { // returned. availableChanBandwidth(channelID uint64, amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) + + // firstHopCustomBlob returns the custom blob for the first hop of the + // payment, if available. + firstHopCustomBlob() fn.Option[tlv.Blob] +} + +// TlvTrafficShaper is an interface that allows the sender to determine if a +// payment should be carried by a channel based on the TLV records that may be +// present in the `update_add_htlc` message or the channel commitment itself. +type TlvTrafficShaper interface { + AuxHtlcModifier + + // ShouldHandleTraffic is called in order to check if the channel + // identified by the provided channel ID may have external mechanisms + // that would allow it to carry out the payment. + ShouldHandleTraffic(cid lnwire.ShortChannelID, + fundingBlob fn.Option[tlv.Blob]) (bool, error) + + // PaymentBandwidth returns the available bandwidth for a custom channel + // decided by the given channel aux blob and HTLC blob. A return value + // of 0 means there is no bandwidth available. To find out if a channel + // 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, + htlcAmt lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) +} + +// AuxHtlcModifier is an interface that allows the sender to modify the outgoing +// HTLC of a payment by changing the amount or the wire message tlv records. +type AuxHtlcModifier interface { + // ProduceHtlcExtraData is a function that, based on the previous extra + // data blob of an HTLC, may produce a different blob or modify the + // amount of bitcoin this htlc should carry. + ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, + htlcCustomRecords lnwire.CustomRecords) (lnwire.MilliSatoshi, + lnwire.CustomRecords, error) } // getLinkQuery is the function signature used to lookup a link. @@ -29,8 +70,10 @@ type getLinkQuery func(lnwire.ShortChannelID) ( // uses the link lookup provided to query the link for our latest local channel // balances. type bandwidthManager struct { - getLink getLinkQuery - localChans map[lnwire.ShortChannelID]struct{} + getLink getLinkQuery + localChans map[lnwire.ShortChannelID]struct{} + firstHopBlob fn.Option[tlv.Blob] + trafficShaper fn.Option[TlvTrafficShaper] } // newBandwidthManager creates a bandwidth manager for the source node provided @@ -40,11 +83,14 @@ type bandwidthManager struct { // allows us to reduce the number of extraneous attempts as we can skip channels // that are inactive, or just don't have enough bandwidth to carry the payment. func newBandwidthManager(graph Graph, sourceNode route.Vertex, - linkQuery getLinkQuery) (*bandwidthManager, error) { + linkQuery getLinkQuery, firstHopBlob fn.Option[tlv.Blob], + trafficShaper fn.Option[TlvTrafficShaper]) (*bandwidthManager, error) { manager := &bandwidthManager{ - getLink: linkQuery, - localChans: make(map[lnwire.ShortChannelID]struct{}), + getLink: linkQuery, + localChans: make(map[lnwire.ShortChannelID]struct{}), + firstHopBlob: firstHopBlob, + trafficShaper: trafficShaper, } // First, we'll collect the set of outbound edges from the target @@ -89,17 +135,112 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, return 0 } - // If our link isn't currently in a state where it can add another + // bandwidthResult is an inline type that we'll use to pass the + // bandwidth result from the external traffic shaper to the main logic + // below. + type bandwidthResult struct { + // bandwidth is the available bandwidth for the channel as + // reported by the external traffic shaper. If the external + // traffic shaper is not handling the channel, this value will + // be fn.None + bandwidth fn.Option[lnwire.MilliSatoshi] + + // htlcAmount is the amount we're going to use to check if we + // can add another HTLC to the channel. If the external traffic + // shaper is handling the channel, we'll use 0 to just sanity + // check the number of HTLCs on the channel, since we don't know + // the actual HTLC amount that will be sent. + htlcAmount fn.Option[lnwire.MilliSatoshi] + } + + var ( + // We will pass the link bandwidth to the external traffic + // shaper. This is the current best estimate for the available + // bandwidth for the link. + linkBandwidth = link.Bandwidth() + + bandwidthErr = func(err error) fn.Result[bandwidthResult] { + return fn.Err[bandwidthResult](err) + } + ) + + result, err := fn.MapOptionZ( + b.trafficShaper, + func(ts TlvTrafficShaper) fn.Result[bandwidthResult] { + fundingBlob := link.FundingCustomBlob() + shouldHandle, err := ts.ShouldHandleTraffic( + cid, fundingBlob, + ) + if err != nil { + return bandwidthErr(fmt.Errorf("traffic "+ + "shaper failed to decide whether to "+ + "handle traffic: %w", err)) + } + + log.Debugf("ShortChannelID=%v: external traffic "+ + "shaper is handling traffic: %v", cid, + shouldHandle) + + // If this channel isn't handled by the external traffic + // shaper, we'll return early. + if !shouldHandle { + return fn.Ok(bandwidthResult{}) + } + + // Ask for a specific bandwidth to be used for the + // channel. + commitmentBlob := link.CommitmentCustomBlob() + auxBandwidth, err := ts.PaymentBandwidth( + b.firstHopBlob, commitmentBlob, linkBandwidth, + amount, + ) + if err != nil { + return bandwidthErr(fmt.Errorf("failed to get "+ + "bandwidth from external traffic "+ + "shaper: %w", err)) + } + + log.Debugf("ShortChannelID=%v: external traffic "+ + "shaper reported available bandwidth: %v", cid, + auxBandwidth) + + // We don't know the actual HTLC amount that will be + // sent using the custom channel. But we'll still want + // to make sure we can add another HTLC, using the + // MayAddOutgoingHtlc method below. Passing 0 into that + // method will use the minimum HTLC value for the + // channel, which is okay to just check we don't exceed + // the max number of HTLCs on the channel. A proper + // balance check is done elsewhere. + return fn.Ok(bandwidthResult{ + bandwidth: fn.Some(auxBandwidth), + htlcAmount: fn.Some[lnwire.MilliSatoshi](0), + }) + }, + ).Unpack() + if err != nil { + log.Errorf("ShortChannelID=%v: failed to get bandwidth from "+ + "external traffic shaper: %v", cid, err) + + return 0 + } + + htlcAmount := result.htlcAmount.UnwrapOr(amount) + + // If our link isn't currently in a state where it can add another // outgoing htlc, treat the link as unusable. - if err := link.MayAddOutgoingHtlc(amount); err != nil { - log.Warnf("ShortChannelID=%v: cannot add outgoing htlc: %v", - cid, err) + if err := link.MayAddOutgoingHtlc(htlcAmount); err != nil { + log.Warnf("ShortChannelID=%v: cannot add outgoing "+ + "htlc with amount %v: %v", cid, htlcAmount, err) return 0 } - // Otherwise, we'll return the current best estimate for the available - // bandwidth for the link. - return link.Bandwidth() + // If the external traffic shaper determined the bandwidth, we'll return + // that value, even if it is zero (which would mean no bandwidth is + // available on that channel). + reportedBandwidth := result.bandwidth.UnwrapOr(linkBandwidth) + + return reportedBandwidth } // availableChanBandwidth returns the total available bandwidth for a channel @@ -116,3 +257,9 @@ func (b *bandwidthManager) availableChanBandwidth(channelID uint64, return b.getBandwidth(shortID, amount), true } + +// firstHopCustomBlob returns the custom blob for the first hop of the payment, +// if available. +func (b *bandwidthManager) firstHopCustomBlob() fn.Option[tlv.Blob] { + return b.firstHopBlob +} diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index ef12d69737..4872b5a7ec 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -5,8 +5,10 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" ) @@ -115,6 +117,8 @@ func TestBandwidthManager(t *testing.T) { m, err := newBandwidthManager( g, sourceNode.pubkey, testCase.linkQuery, + fn.None[[]byte](), + fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), ) require.NoError(t, err) @@ -126,3 +130,35 @@ func TestBandwidthManager(t *testing.T) { }) } } + +type mockTrafficShaper struct{} + +// ShouldHandleTraffic is called in order to check if the channel identified +// by the provided channel ID may have external mechanisms that would +// allow it to carry out the payment. +func (*mockTrafficShaper) ShouldHandleTraffic(_ lnwire.ShortChannelID, + _ fn.Option[tlv.Blob]) (bool, error) { + + return true, nil +} + +// PaymentBandwidth returns the available bandwidth for a custom channel +// decided by the given channel aux blob and HTLC blob. A return value +// of 0 means there is no bandwidth available. To find out if a channel +// 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) { + + return linkBandwidth, nil +} + +// ProduceHtlcExtraData is a function that, based on the previous extra +// data blob of an HTLC, may produce a different blob or modify the +// amount of bitcoin this htlc should carry. +func (*mockTrafficShaper) ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, + _ lnwire.CustomRecords) (lnwire.MilliSatoshi, lnwire.CustomRecords, + error) { + + return totalAmount, nil, nil +} diff --git a/routing/integrated_routing_context_test.go b/routing/integrated_routing_context_test.go index ee6fed295a..061dcfc3aa 100644 --- a/routing/integrated_routing_context_test.go +++ b/routing/integrated_routing_context_test.go @@ -8,9 +8,11 @@ import ( "time" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/require" ) @@ -35,6 +37,10 @@ func (m *mockBandwidthHints) availableChanBandwidth(channelID uint64, return balance, ok } +func (m *mockBandwidthHints) firstHopCustomBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + // integratedRoutingContext defines the context in which integrated routing // tests run. type integratedRoutingContext struct { @@ -181,7 +187,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32, FinalCLTVDelta: uint16(c.finalExpiry), FeeLimit: lnwire.MaxMilliSatoshi, Target: c.target.pubkey, - PaymentAddr: &paymentAddr, + PaymentAddr: fn.Some(paymentAddr), DestFeatures: lnwire.NewFeatureVector( baseFeatureBits, lnwire.Features, ), @@ -227,6 +233,9 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32, // Find a route. route, err := session.RequestRoute( amtRemaining, lnwire.MaxMilliSatoshi, inFlightHtlcs, 0, + lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType: []byte{1, 2, 3}, + }, ) if err != nil { return attempts, err diff --git a/routing/integrated_routing_test.go b/routing/integrated_routing_test.go index 0e873d6646..4a2447b487 100644 --- a/routing/integrated_routing_test.go +++ b/routing/integrated_routing_test.go @@ -296,7 +296,7 @@ func testMppSend(t *testing.T, testCase *mppSendTestCase) { case err == nil && testCase.expectedFailure: t.Fatal("expected payment to fail") case err != nil && !testCase.expectedFailure: - t.Fatal("expected payment to succeed") + t.Fatalf("expected payment to succeed, got %v", err) } if len(attempts) != testCase.expectedAttempts { diff --git a/routing/mock_test.go b/routing/mock_test.go index 306c182107..99d56c68bd 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -9,12 +9,14 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/shards" + "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/mock" ) @@ -104,7 +106,8 @@ type mockPaymentSessionSourceOld struct { var _ PaymentSessionSource = (*mockPaymentSessionSourceOld)(nil) func (m *mockPaymentSessionSourceOld) NewPaymentSession( - _ *LightningPayment) (PaymentSession, error) { + _ *LightningPayment, _ fn.Option[tlv.Blob], + _ fn.Option[TlvTrafficShaper]) (PaymentSession, error) { return &mockPaymentSessionOld{ routes: m.routes, @@ -166,7 +169,8 @@ type mockPaymentSessionOld struct { var _ PaymentSession = (*mockPaymentSessionOld)(nil) func (m *mockPaymentSessionOld) RequestRoute(_, _ lnwire.MilliSatoshi, - _, height uint32) (*route.Route, error) { + _, height uint32, _ lnwire.CustomRecords) (*route.Route, + error) { if m.release != nil { m.release <- struct{}{} @@ -630,9 +634,10 @@ type mockPaymentSessionSource struct { var _ PaymentSessionSource = (*mockPaymentSessionSource)(nil) func (m *mockPaymentSessionSource) NewPaymentSession( - payment *LightningPayment) (PaymentSession, error) { + payment *LightningPayment, firstHopBlob fn.Option[tlv.Blob], + tlvShaper fn.Option[TlvTrafficShaper]) (PaymentSession, error) { - args := m.Called(payment) + args := m.Called(payment, firstHopBlob, tlvShaper) return args.Get(0).(PaymentSession), args.Error(1) } @@ -690,9 +695,12 @@ type mockPaymentSession struct { var _ PaymentSession = (*mockPaymentSession)(nil) func (m *mockPaymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) { + activeShards, height uint32, + firstHopCustomRecords lnwire.CustomRecords) (*route.Route, error) { - args := m.Called(maxAmt, feeLimit, activeShards, height) + args := m.Called( + maxAmt, feeLimit, activeShards, height, firstHopCustomRecords, + ) // Type assertion on nil will fail, so we check and return here. if args.Get(0) == nil { @@ -897,6 +905,14 @@ func (m *mockLink) MayAddOutgoingHtlc(_ lnwire.MilliSatoshi) error { return m.mayAddOutgoingErr } +func (m *mockLink) FundingCustomBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + +func (m *mockLink) CommitmentCustomBlob() fn.Option[tlv.Blob] { + return fn.None[tlv.Blob]() +} + type mockShardTracker struct { mock.Mock } diff --git a/routing/pathfind.go b/routing/pathfind.go index 01325c2238..43eae71036 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -111,7 +111,7 @@ type finalHopParams struct { cltvDelta uint16 records record.CustomSet - paymentAddr *[32]byte + paymentAddr fn.Option[[32]byte] // metadata is additional data that is sent along with the payment to // the payee. @@ -226,7 +226,7 @@ func newRoute(sourceVertex route.Vertex, // If we're attaching a payment addr but the receiver // doesn't support both TLV and payment addrs, fail. payAddr := supports(lnwire.PaymentAddrOptional) - if !payAddr && finalHop.paymentAddr != nil { + if !payAddr && finalHop.paymentAddr.IsSome() { return nil, errors.New("cannot attach " + "payment addr") } @@ -234,12 +234,9 @@ func newRoute(sourceVertex route.Vertex, // Otherwise attach the mpp record if it exists. // TODO(halseth): move this to payment life cycle, // where AMP options are set. - if finalHop.paymentAddr != nil { - mpp = record.NewMPP( - finalHop.totalAmt, - *finalHop.paymentAddr, - ) - } + finalHop.paymentAddr.WhenSome(func(addr [32]byte) { + mpp = record.NewMPP(finalHop.totalAmt, addr) + }) metadata = finalHop.metadata @@ -452,7 +449,7 @@ type RestrictParams struct { // PaymentAddr is a random 32-byte value generated by the receiver to // mitigate probing vectors and payment sniping attacks on overpaid // invoices. - PaymentAddr *[32]byte + PaymentAddr fn.Option[[32]byte] // Amp signals to the pathfinder that this payment is an AMP payment // and therefore it needs to account for additional AMP data in the @@ -466,6 +463,10 @@ type RestrictParams struct { // BlindedPaymentPathSet is necessary to determine the hop size of the // last/exit hop. BlindedPaymentPathSet *BlindedPaymentPathSet + + // FirstHopCustomRecords includes any records that should be included in + // the update_add_htlc message towards our peer. + FirstHopCustomRecords lnwire.CustomRecords } // PathFindingConfig defines global parameters that control the trade-off in @@ -524,7 +525,16 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{}, max = bandwidth } - total += bandwidth + var overflow bool + total, overflow = overflowSafeAdd(total, bandwidth) + if overflow { + // If the current total and the bandwidth would + // overflow the maximum value, we set the total to the + // maximum value. Which is more milli-satoshis than are + // in existence anyway, so the actual value is + // irrelevant. + total = lnwire.MilliSatoshi(math.MaxUint64) + } return nil } @@ -595,7 +605,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // checking that it supports the features we need. If the caller has a // payment address to attach, check that our destination feature vector // supports them. - if r.PaymentAddr != nil && + if r.PaymentAddr.IsSome() && !features.HasFeature(lnwire.PaymentAddrOptional) { return nil, 0, errNoPaymentAddr @@ -1422,9 +1432,9 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32, } var mpp *record.MPP - if r.PaymentAddr != nil { - mpp = record.NewMPP(amount, *r.PaymentAddr) - } + r.PaymentAddr.WhenSome(func(addr [32]byte) { + mpp = record.NewMPP(amount, addr) + }) var amp *record.AMP if r.Amp != nil { @@ -1446,3 +1456,15 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32, // The final hop does not have a short chanID set. return finalHop.PayloadSize(0) } + +// overflowSafeAdd adds two MilliSatoshi values and returns the result. If an +// overflow could occur, zero is returned instead and the boolean is set to +// true. +func overflowSafeAdd(x, y lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) { + if y > math.MaxUint64-x { + // Overflow would occur, return 0 and set overflow flag. + return 0, true + } + + return x + y, false +} diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 38942a31d2..72f71600dd 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -1367,7 +1367,7 @@ func TestNewRoute(t *testing.T) { // overwrite the final hop's feature vector in the graph. destFeatures *lnwire.FeatureVector - paymentAddr *[32]byte + paymentAddr fn.Option[[32]byte] // metadata is the payment metadata to attach to the route. metadata []byte @@ -1446,7 +1446,7 @@ func TestNewRoute(t *testing.T) { // a fee to receive the payment. name: "two hop single shot mpp", destFeatures: tlvPayAddrFeatures, - paymentAddr: &testPaymentAddr, + paymentAddr: fn.Some(testPaymentAddr), paymentAmount: 100000, hops: []*models.CachedEdgePolicy{ createHop(0, 1000, 1000000, 10), @@ -1911,7 +1911,7 @@ func runDestPaymentAddr(t *testing.T, useCache bool) { luoji := ctx.keyFromAlias("luoji") // Add payment address w/o any invoice features. - ctx.restrictParams.PaymentAddr = &[32]byte{1} + ctx.restrictParams.PaymentAddr = fn.Some([32]byte{1}) // Add empty destination features. This should cause us to fail, since // this overrides anything in the graph. @@ -2955,7 +2955,7 @@ func runInboundFees(t *testing.T, useCache bool) { ctx := newPathFindingTestContext(t, useCache, testChannels, "a") payAddr := [32]byte{1} - ctx.restrictParams.PaymentAddr = &payAddr + ctx.restrictParams.PaymentAddr = fn.Some(payAddr) ctx.restrictParams.DestFeatures = tlvPayAddrFeatures const ( @@ -2974,7 +2974,7 @@ func runInboundFees(t *testing.T, useCache bool) { amt: paymentAmt, cltvDelta: finalHopCLTV, records: nil, - paymentAddr: &payAddr, + paymentAddr: fn.Some(payAddr), totalAmt: paymentAmt, }, nil, @@ -3469,7 +3469,7 @@ func TestLastHopPayloadSize(t *testing.T) { { name: "Non blinded final hop", restrictions: &RestrictParams{ - PaymentAddr: paymentAddr, + PaymentAddr: fn.Some(*paymentAddr), DestCustomRecords: customRecords, Metadata: metadata, Amp: ampOptions, @@ -3501,12 +3501,10 @@ func TestLastHopPayloadSize(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - var mpp *record.MPP - if tc.restrictions.PaymentAddr != nil { - mpp = record.NewMPP( - tc.amount, *tc.restrictions.PaymentAddr, - ) - } + mpp := fn.MapOptionZ(tc.restrictions.PaymentAddr, + func(addr [32]byte) *record.MPP { + return record.NewMPP(tc.amount, addr) + }) // In case it's an AMP payment we use the max AMP record // size to estimate the final hop size. diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 5244d4d636..43e646e192 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -11,11 +11,13 @@ import ( sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/shards" + "github.com/lightningnetwork/lnd/tlv" ) // ErrPaymentLifecycleExiting is used when waiting for htlc attempt result, but @@ -25,12 +27,13 @@ var ErrPaymentLifecycleExiting = errors.New("payment lifecycle exiting") // paymentLifecycle holds all information about the current state of a payment // needed to resume if from any point. type paymentLifecycle struct { - router *ChannelRouter - feeLimit lnwire.MilliSatoshi - identifier lntypes.Hash - paySession PaymentSession - shardTracker shards.ShardTracker - currentHeight int32 + router *ChannelRouter + feeLimit lnwire.MilliSatoshi + identifier lntypes.Hash + paySession PaymentSession + shardTracker shards.ShardTracker + currentHeight int32 + firstHopCustomRecords lnwire.CustomRecords // quit is closed to signal the sub goroutines of the payment lifecycle // to stop. @@ -52,18 +55,19 @@ type paymentLifecycle struct { // newPaymentLifecycle initiates a new payment lifecycle and returns it. func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi, identifier lntypes.Hash, paySession PaymentSession, - shardTracker shards.ShardTracker, - currentHeight int32) *paymentLifecycle { + shardTracker shards.ShardTracker, currentHeight int32, + firstHopCustomRecords lnwire.CustomRecords) *paymentLifecycle { p := &paymentLifecycle{ - router: r, - feeLimit: feeLimit, - identifier: identifier, - paySession: paySession, - shardTracker: shardTracker, - currentHeight: currentHeight, - quit: make(chan struct{}), - resultCollected: make(chan error, 1), + router: r, + feeLimit: feeLimit, + identifier: identifier, + paySession: paySession, + shardTracker: shardTracker, + currentHeight: currentHeight, + quit: make(chan struct{}), + resultCollected: make(chan error, 1), + firstHopCustomRecords: firstHopCustomRecords, } // Mount the result collector. @@ -273,6 +277,13 @@ lifecycle: log.Tracef("Found route: %s", spew.Sdump(rt.Hops)) + // Allow the traffic shaper to add custom records to the + // outgoing HTLC and also adjust the amount if needed. + err = p.amendFirstHopData(rt) + if err != nil { + return exitWithErr(err) + } + // We found a route to try, create a new HTLC attempt to try. attempt, err := p.registerAttempt(rt, ps.RemainingAmt) if err != nil { @@ -364,6 +375,7 @@ func (p *paymentLifecycle) requestRoute( rt, err := p.paySession.RequestRoute( ps.RemainingAmt, remainingFees, uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight), + p.firstHopCustomRecords, ) // Exit early if there's no error. @@ -665,8 +677,10 @@ func (p *paymentLifecycle) createNewPaymentAttempt(rt *route.Route, func (p *paymentLifecycle) sendAttempt( attempt *channeldb.HTLCAttempt) (*attemptResult, error) { - log.Debugf("Sending HTLC attempt(id=%v, amt=%v) for payment %v", - attempt.AttemptID, attempt.Route.TotalAmount, p.identifier) + log.Debugf("Sending HTLC attempt(id=%v, total_amt=%v, first_hop_amt=%d"+ + ") for payment %v", attempt.AttemptID, + attempt.Route.TotalAmount, attempt.Route.FirstHopAmount.Val, + p.identifier) rt := attempt.Route @@ -677,9 +691,10 @@ func (p *paymentLifecycle) sendAttempt( // this packet will be used to route the payment through the network, // starting with the first-hop. htlcAdd := &lnwire.UpdateAddHTLC{ - Amount: rt.TotalAmount, - Expiry: rt.TotalTimeLock, - PaymentHash: *attempt.Hash, + Amount: rt.FirstHopAmount.Val.Int(), + Expiry: rt.TotalTimeLock, + PaymentHash: *attempt.Hash, + CustomRecords: rt.FirstHopWireCustomRecords, } // Generate the raw encoded sphinx packet to be included along @@ -718,6 +733,75 @@ func (p *paymentLifecycle) sendAttempt( }, nil } +// amendFirstHopData is a function that calls the traffic shaper to allow it to +// add custom records to the outgoing HTLC and also adjust the amount if +// needed. +func (p *paymentLifecycle) amendFirstHopData(rt *route.Route) error { + // The first hop amount on the route is the full route amount if not + // overwritten by the traffic shaper. So we set the initial value now + // and potentially overwrite it later. + rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( + tlv.NewBigSizeT(rt.TotalAmount), + ) + + // By default, we set the first hop custom records to the initial + // value requested by the RPC. The traffic shaper may overwrite this + // value. + rt.FirstHopWireCustomRecords = p.firstHopCustomRecords + + // extraDataRequest is a helper struct to pass the custom records and + // amount back from the traffic shaper. + type extraDataRequest struct { + customRecords fn.Option[lnwire.CustomRecords] + + amount fn.Option[lnwire.MilliSatoshi] + } + + // If a hook exists that may affect our outgoing message, we call it now + // and apply its side effects to the UpdateAddHTLC message. + result, err := fn.MapOptionZ( + p.router.cfg.TrafficShaper, + func(ts TlvTrafficShaper) fn.Result[extraDataRequest] { + newAmt, newRecords, err := ts.ProduceHtlcExtraData( + rt.TotalAmount, p.firstHopCustomRecords, + ) + if err != nil { + return fn.Err[extraDataRequest](err) + } + + // Make sure we only received valid records. + if err := newRecords.Validate(); err != nil { + return fn.Err[extraDataRequest](err) + } + + log.Debugf("TLV traffic shaper returned custom "+ + "records %v and amount %d msat for HTLC", + spew.Sdump(newRecords), newAmt) + + return fn.Ok(extraDataRequest{ + customRecords: fn.Some(newRecords), + amount: fn.Some(newAmt), + }) + }, + ).Unpack() + if err != nil { + return fmt.Errorf("traffic shaper failed to produce extra "+ + "data: %w", err) + } + + // Apply the side effects to the UpdateAddHTLC message. + result.customRecords.WhenSome(func(records lnwire.CustomRecords) { + rt.FirstHopWireCustomRecords = records + }) + result.amount.WhenSome(func(amount lnwire.MilliSatoshi) { + rt.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0]( + tlv.NewBigSizeT(amount), + ) + }) + + return nil +} + // failAttemptAndPayment fails both the payment and its attempt via the // router's control tower, which marks the payment as failed in db. func (p *paymentLifecycle) failPaymentAndAttempt( diff --git a/routing/payment_lifecycle_test.go b/routing/payment_lifecycle_test.go index 34c8d6c17a..315c1bad58 100644 --- a/routing/payment_lifecycle_test.go +++ b/routing/payment_lifecycle_test.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnmock" "github.com/lightningnetwork/lnd/lntest/wait" @@ -28,7 +29,11 @@ func createTestPaymentLifecycle() *paymentLifecycle { paymentHash := lntypes.Hash{1, 2, 3} quitChan := make(chan struct{}) rt := &ChannelRouter{ - cfg: &Config{}, + cfg: &Config{ + TrafficShaper: fn.Some[TlvTrafficShaper]( + &mockTrafficShaper{}, + ), + }, quit: quitChan, } @@ -78,6 +83,9 @@ func newTestPaymentLifecycle(t *testing.T) (*paymentLifecycle, *mockers) { Payer: mockPayer, Clock: mockClock, MissionControl: mockMissionControl, + TrafficShaper: fn.Some[TlvTrafficShaper]( + &mockTrafficShaper{}, + ), }, quit: quitChan, } @@ -89,7 +97,7 @@ func newTestPaymentLifecycle(t *testing.T) (*paymentLifecycle, *mockers) { // Create a test payment lifecycle with no fee limit and no timeout. p := newPaymentLifecycle( rt, noFeeLimit, paymentHash, mockPaymentSession, - mockShardTracker, 0, + mockShardTracker, 0, nil, ) // Create a mock payment which is returned from mockControlTower. @@ -372,6 +380,7 @@ func TestRequestRouteSucceed(t *testing.T) { // Mock the paySession's `RequestRoute` method to return no error. paySession.On("RequestRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, ).Return(dummyRoute, nil) result, err := p.requestRoute(ps) @@ -408,6 +417,7 @@ func TestRequestRouteHandleCriticalErr(t *testing.T) { // Mock the paySession's `RequestRoute` method to return an error. paySession.On("RequestRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, ).Return(nil, errDummy) result, err := p.requestRoute(ps) @@ -442,6 +452,7 @@ func TestRequestRouteHandleNoRouteErr(t *testing.T) { // type. m.paySession.On("RequestRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, ).Return(nil, errNoTlvPayload) // The payment should be failed with reason no route. @@ -489,6 +500,7 @@ func TestRequestRouteFailPaymentError(t *testing.T) { // Mock the paySession's `RequestRoute` method to return an error. paySession.On("RequestRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, + mock.Anything, ).Return(nil, errNoTlvPayload) result, err := p.requestRoute(ps) @@ -865,7 +877,7 @@ func TestResumePaymentFailOnRequestRouteErr(t *testing.T) { // 4. mock requestRoute to return an error. m.paySession.On("RequestRoute", paymentAmt, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(nil, errDummy).Once() // Send the payment and assert it failed. @@ -911,7 +923,7 @@ func TestResumePaymentFailOnRegisterAttemptErr(t *testing.T) { // 4. mock requestRoute to return an route. m.paySession.On("RequestRoute", paymentAmt, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(rt, nil).Once() // 5. mock shardTracker used in `createNewPaymentAttempt` to return an @@ -971,7 +983,7 @@ func TestResumePaymentFailOnSendAttemptErr(t *testing.T) { // 4. mock requestRoute to return an route. m.paySession.On("RequestRoute", paymentAmt, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(rt, nil).Once() // 5. mock `registerAttempt` to return an attempt. @@ -1063,7 +1075,7 @@ func TestResumePaymentSuccess(t *testing.T) { // 1.4. mock requestRoute to return an route. m.paySession.On("RequestRoute", paymentAmt, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(rt, nil).Once() // 1.5. mock `registerAttempt` to return an attempt. @@ -1164,7 +1176,7 @@ func TestResumePaymentSuccessWithTwoAttempts(t *testing.T) { // 1.4. mock requestRoute to return an route. m.paySession.On("RequestRoute", paymentAmt, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(rt, nil).Once() // Create two attempt IDs here. @@ -1226,7 +1238,7 @@ func TestResumePaymentSuccessWithTwoAttempts(t *testing.T) { // 2.4. mock requestRoute to return an route. m.paySession.On("RequestRoute", paymentAmt/2, p.feeLimit, uint32(ps.NumAttemptsInFlight), - uint32(p.currentHeight), + uint32(p.currentHeight), mock.Anything, ).Return(rt, nil).Once() // 2.5. mock `registerAttempt` to return an attempt. diff --git a/routing/payment_session.go b/routing/payment_session.go index 00b4ab70ed..3bd718e9d8 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -139,7 +139,9 @@ type PaymentSession interface { // A noRouteError is returned if a non-critical error is encountered // during path finding. RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) + activeShards, height uint32, + firstHopCustomRecords lnwire.CustomRecords) (*route.Route, + error) // UpdateAdditionalEdge takes an additional channel edge policy // (private channels) and applies the update from the message. Returns @@ -243,7 +245,8 @@ func newPaymentSession(p *LightningPayment, selfNode route.Vertex, // NOTE: This function is safe for concurrent access. // NOTE: Part of the PaymentSession interface. func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) { + activeShards, height uint32, + firstHopCustomRecords lnwire.CustomRecords) (*route.Route, error) { if p.empty { return nil, errEmptyPaySession @@ -265,16 +268,17 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, // to our destination, respecting the recommendations from // MissionControl. restrictions := &RestrictParams{ - ProbabilitySource: p.missionControl.GetProbability, - FeeLimit: feeLimit, - OutgoingChannelIDs: p.payment.OutgoingChannelIDs, - LastHop: p.payment.LastHop, - CltvLimit: cltvLimit, - DestCustomRecords: p.payment.DestCustomRecords, - DestFeatures: p.payment.DestFeatures, - PaymentAddr: p.payment.PaymentAddr, - Amp: p.payment.amp, - Metadata: p.payment.Metadata, + ProbabilitySource: p.missionControl.GetProbability, + FeeLimit: feeLimit, + OutgoingChannelIDs: p.payment.OutgoingChannelIDs, + LastHop: p.payment.LastHop, + CltvLimit: cltvLimit, + DestCustomRecords: p.payment.DestCustomRecords, + DestFeatures: p.payment.DestFeatures, + PaymentAddr: p.payment.PaymentAddr, + Amp: p.payment.amp, + Metadata: p.payment.Metadata, + FirstHopCustomRecords: firstHopCustomRecords, } finalHtlcExpiry := int32(height) + int32(finalCltvDelta) @@ -284,9 +288,9 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, // client-side MTU that we'll attempt to respect at all times. maxShardActive := p.payment.MaxShardAmt != nil if maxShardActive && maxAmt > *p.payment.MaxShardAmt { - p.log.Debug("Clamping payment attempt from %v to %v due to "+ - "max shard size of %v", maxAmt, - *p.payment.MaxShardAmt, maxAmt) + p.log.Debugf("Clamping payment attempt from %v to %v due to "+ + "max shard size of %v", maxAmt, *p.payment.MaxShardAmt, + maxAmt) maxAmt = *p.payment.MaxShardAmt } @@ -340,7 +344,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, // record. If it has a blinded path though, then we // can split. Split payments to blinded paths won't have // MPP records. - if p.payment.PaymentAddr == nil && + if p.payment.PaymentAddr.IsNone() && p.payment.BlindedPathSet == nil { p.log.Debugf("not splitting because payment " + diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go index 46e7a42aa1..c89d6a8e52 100644 --- a/routing/payment_session_source.go +++ b/routing/payment_session_source.go @@ -4,8 +4,10 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" ) @@ -49,12 +51,14 @@ type SessionSource struct { // view from Mission Control. An optional set of routing hints can be provided // in order to populate additional edges to explore when finding a path to the // payment's destination. -func (m *SessionSource) NewPaymentSession(p *LightningPayment) ( - PaymentSession, error) { +func (m *SessionSource) NewPaymentSession(p *LightningPayment, + firstHopBlob fn.Option[tlv.Blob], + trafficShaper fn.Option[TlvTrafficShaper]) (PaymentSession, error) { getBandwidthHints := func(graph Graph) (bandwidthHints, error) { return newBandwidthManager( graph, m.SourceNode.PubKeyBytes, m.GetLink, + firstHopBlob, trafficShaper, ) } diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index f6873aa752..34f8356820 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -235,6 +235,9 @@ func TestRequestRoute(t *testing.T) { route, err := session.RequestRoute( payment.Amount, payment.FeeLimit, 0, height, + lnwire.CustomRecords{ + lnwire.MinCustomRecordsTlvType + 123: []byte{1, 2, 3}, + }, ) if err != nil { t.Fatal(err) diff --git a/routing/route/route.go b/routing/route/route.go index 0516cc7a22..9aa28759bc 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -488,6 +488,26 @@ type Route struct { // Hops contains details concerning the specific forwarding details at // each hop. Hops []*Hop + + // FirstHopAmount is the amount that should actually be sent to the + // first hop in the route. This is only different from TotalAmount above + // for custom channels where the on-chain amount doesn't necessarily + // reflect all the value of an outgoing payment. + FirstHopAmount tlv.RecordT[ + tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi], + ] + + // FirstHopWireCustomRecords is a set of custom records that should be + // included in the wire message sent to the first hop. This is only set + // on custom channels and is used to include additional information + // about the actual value of the payment. + // + // NOTE: Since these records already represent TLV records, and we + // enforce them to be in the custom range (e.g. >= 65536), we don't use + // another parent record type here. Instead, when serializing the Route + // we merge the TLV records together with the custom records and encode + // everything as a single TLV stream. + FirstHopWireCustomRecords lnwire.CustomRecords } // Copy returns a deep copy of the Route. diff --git a/routing/router.go b/routing/router.go index 0b6a9beacd..04096fa67f 100644 --- a/routing/router.go +++ b/routing/router.go @@ -29,6 +29,7 @@ import ( "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/shards" + "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" ) @@ -154,7 +155,10 @@ type PaymentSessionSource interface { // routes to the given target. An optional set of routing hints can be // provided in order to populate additional edges to explore when // finding a path to the payment's destination. - NewPaymentSession(p *LightningPayment) (PaymentSession, error) + NewPaymentSession(p *LightningPayment, + firstHopBlob fn.Option[tlv.Blob], + trafficShaper fn.Option[TlvTrafficShaper]) (PaymentSession, + error) // NewPaymentSessionEmpty creates a new paymentSession instance that is // empty, and will be exhausted immediately. Used for failure reporting @@ -290,6 +294,10 @@ type Config struct { // // TODO(yy): remove it once the root cause of stuck payments is found. ClosedSCIDs map[lnwire.ShortChannelID]struct{} + + // TrafficShaper is an optional traffic shaper that can be used to + // control the outgoing channel of a payment. + TrafficShaper fn.Option[TlvTrafficShaper] } // EdgeLocator is a struct used to identify a specific edge. @@ -517,6 +525,7 @@ func (r *ChannelRouter) FindRoute(req *RouteRequest) (*route.Route, float64, // eliminate certain routes early on in the path finding process. bandwidthHints, err := newBandwidthManager( r.cfg.RoutingGraph, r.cfg.SelfNode, r.cfg.GetLink, + fn.None[tlv.Blob](), r.cfg.TrafficShaper, ) if err != nil { return nil, 0, err @@ -853,7 +862,7 @@ type LightningPayment struct { // PaymentAddr is the payment address specified by the receiver. This // field should be a random 32-byte nonce presented in the receiver's // invoice to prevent probing of the destination. - PaymentAddr *[32]byte + PaymentAddr fn.Option[[32]byte] // PaymentRequest is an optional payment request that this payment is // attempting to complete. @@ -865,6 +874,11 @@ type LightningPayment struct { // fail. DestCustomRecords record.CustomSet + // FirstHopCustomRecords are the TLV records that are to be sent to the + // first hop of this payment. These records will be transmitted via the + // wire message and therefore do not affect the onion payload size. + FirstHopCustomRecords lnwire.CustomRecords + // MaxParts is the maximum number of partial payments that may be used // to complete the full amount. MaxParts uint32 @@ -948,6 +962,7 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, return r.sendPayment( context.Background(), payment.FeeLimit, payment.Identifier(), payment.PayAttemptTimeout, paySession, shardTracker, + payment.FirstHopCustomRecords, ) } @@ -968,6 +983,7 @@ func (r *ChannelRouter) SendPaymentAsync(ctx context.Context, _, _, err := r.sendPayment( ctx, payment.FeeLimit, payment.Identifier(), payment.PayAttemptTimeout, ps, st, + payment.FirstHopCustomRecords, ) if err != nil { log.Errorf("Payment %x failed: %v", @@ -1002,10 +1018,29 @@ func spewPayment(payment *LightningPayment) lnutils.LogClosure { func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( PaymentSession, shards.ShardTracker, error) { + // Assemble any custom data we want to send to the first hop only. + var firstHopData fn.Option[tlv.Blob] + if len(payment.FirstHopCustomRecords) > 0 { + if err := payment.FirstHopCustomRecords.Validate(); err != nil { + return nil, nil, fmt.Errorf("invalid first hop custom "+ + "records: %w", err) + } + + firstHopBlob, err := payment.FirstHopCustomRecords.Serialize() + if err != nil { + return nil, nil, fmt.Errorf("unable to serialize "+ + "first hop custom records: %w", err) + } + + firstHopData = fn.Some(firstHopBlob) + } + // Before starting the HTLC routing attempt, we'll create a fresh // payment session which will report our errors back to mission // control. - paySession, err := r.cfg.SessionSource.NewPaymentSession(payment) + paySession, err := r.cfg.SessionSource.NewPaymentSession( + payment, firstHopData, r.cfg.TrafficShaper, + ) if err != nil { return nil, nil, err } @@ -1015,10 +1050,11 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( // // TODO(roasbeef): store records as part of creation info? info := &channeldb.PaymentCreationInfo{ - PaymentIdentifier: payment.Identifier(), - Value: payment.Amount, - CreationTime: r.cfg.Clock.Now(), - PaymentRequest: payment.PaymentRequest, + PaymentIdentifier: payment.Identifier(), + Value: payment.Amount, + CreationTime: r.cfg.Clock.Now(), + PaymentRequest: payment.PaymentRequest, + FirstHopCustomRecords: payment.FirstHopCustomRecords, } // Create a new ShardTracker that we'll use during the life cycle of @@ -1027,9 +1063,10 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( switch { // If this is an AMP payment, we'll use the AMP shard tracker. case payment.amp != nil: + addr := payment.PaymentAddr.UnwrapOr([32]byte{}) shardTracker = amp.NewShardTracker( - payment.amp.RootShare, payment.amp.SetID, - *payment.PaymentAddr, payment.Amount, + payment.amp.RootShare, payment.amp.SetID, addr, + payment.Amount, ) // Otherwise we'll use the simple tracker that will map each attempt to @@ -1050,18 +1087,21 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( // SendToRoute sends a payment using the provided route and fails the payment // when an error is returned from the attempt. -func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, - rt *route.Route) (*channeldb.HTLCAttempt, error) { +func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route, + firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt, + error) { - return r.sendToRoute(htlcHash, rt, false) + return r.sendToRoute(htlcHash, rt, false, firstHopCustomRecords) } // SendToRouteSkipTempErr sends a payment using the provided route and fails // the payment ONLY when a terminal error is returned from the attempt. func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash, - rt *route.Route) (*channeldb.HTLCAttempt, error) { + rt *route.Route, + firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt, + error) { - return r.sendToRoute(htlcHash, rt, true) + return r.sendToRoute(htlcHash, rt, true, firstHopCustomRecords) } // sendToRoute attempts to send a payment with the given hash through the @@ -1071,7 +1111,9 @@ func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash, // was initiated, both return values will be non-nil. If skipTempErr is true, // the payment won't be failed unless a terminal error has occurred. func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, - skipTempErr bool) (*channeldb.HTLCAttempt, error) { + skipTempErr bool, + firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt, + error) { log.Debugf("SendToRoute for payment %v with skipTempErr=%v", htlcHash, skipTempErr) @@ -1108,10 +1150,11 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, // Record this payment hash with the ControlTower, ensuring it is not // already in-flight. info := &channeldb.PaymentCreationInfo{ - PaymentIdentifier: paymentIdentifier, - Value: amt, - CreationTime: r.cfg.Clock.Now(), - PaymentRequest: nil, + PaymentIdentifier: paymentIdentifier, + Value: amt, + CreationTime: r.cfg.Clock.Now(), + PaymentRequest: nil, + FirstHopCustomRecords: firstHopCustomRecords, } err := r.cfg.Control.InitPayment(paymentIdentifier, info) @@ -1141,7 +1184,17 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, // - nil payment session (since we already have a route). // - no payment timeout. // - no current block height. - p := newPaymentLifecycle(r, 0, paymentIdentifier, nil, shardTracker, 0) + p := newPaymentLifecycle( + r, 0, paymentIdentifier, nil, shardTracker, 0, + firstHopCustomRecords, + ) + + // Allow the traffic shaper to add custom records to the outgoing HTLC + // and also adjust the amount if needed. + err = p.amendFirstHopData(rt) + if err != nil { + return nil, err + } // We found a route to try, create a new HTLC attempt to try. // @@ -1237,7 +1290,9 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route, func (r *ChannelRouter) sendPayment(ctx context.Context, feeLimit lnwire.MilliSatoshi, identifier lntypes.Hash, paymentAttemptTimeout time.Duration, paySession PaymentSession, - shardTracker shards.ShardTracker) ([32]byte, *route.Route, error) { + shardTracker shards.ShardTracker, + firstHopCustomRecords lnwire.CustomRecords) ([32]byte, *route.Route, + error) { // If the user provides a timeout, we will additionally wrap the context // in a deadline. @@ -1258,11 +1313,16 @@ func (r *ChannelRouter) sendPayment(ctx context.Context, return [32]byte{}, nil, err } + // Validate the custom records before we attempt to send the payment. + if err := firstHopCustomRecords.Validate(); err != nil { + return [32]byte{}, nil, err + } + // Now set up a paymentLifecycle struct with these params, such that we // can resume the payment from the current state. p := newPaymentLifecycle( r, feeLimit, identifier, paySession, shardTracker, - currentHeight, + currentHeight, firstHopCustomRecords, ) return p.resumePayment(ctx) @@ -1307,8 +1367,9 @@ func (e ErrNoChannel) Error() string { // amount is nil, the minimum routable amount is used. To force a specific // outgoing channel, use the outgoingChan parameter. func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi], - hops []route.Vertex, outgoingChan *uint64, - finalCltvDelta int32, payAddr *[32]byte) (*route.Route, error) { + hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32, + payAddr fn.Option[[32]byte], firstHopBlob fn.Option[[]byte]) ( + *route.Route, error) { log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt) @@ -1322,7 +1383,8 @@ func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi], // We'll attempt to obtain a set of bandwidth hints that helps us select // the best outgoing channel to use in case no outgoing channel is set. bandwidthHints, err := newBandwidthManager( - r.cfg.RoutingGraph, r.cfg.SelfNode, r.cfg.GetLink, + r.cfg.RoutingGraph, r.cfg.SelfNode, r.cfg.GetLink, firstHopBlob, + r.cfg.TrafficShaper, ) if err != nil { return nil, err @@ -1465,7 +1527,7 @@ func (r *ChannelRouter) resumePayments() error { noTimeout := time.Duration(0) _, _, err := r.sendPayment( context.Background(), 0, payHash, noTimeout, paySession, - shardTracker, + shardTracker, payment.Info.FirstHopCustomRecords, ) if err != nil { log.Errorf("Resuming payment %v failed: %v", payHash, diff --git a/routing/router_test.go b/routing/router_test.go index 411a6c81cd..53c49f1cfc 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -164,6 +164,9 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, Clock: clock.NewTestClock(time.Unix(1, 0)), ApplyChannelUpdate: graphBuilder.ApplyChannelUpdate, ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper]( + &mockTrafficShaper{}, + ), }) require.NoError(t, router.Start(), "unable to start router") @@ -519,7 +522,7 @@ func TestChannelUpdateValidation(t *testing.T) { // Send off the payment request to the router. The specified route // should be attempted and the channel update should be received by // graph and ignored because it is missing a valid signature. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") _, e1, e2, err = ctx.graph.FetchChannelEdgesByID( @@ -539,7 +542,7 @@ func TestChannelUpdateValidation(t *testing.T) { ctx.graphBuilder.setNextReject(false) // Retry the payment using the same route as before. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) require.Error(t, err, "expected route to fail with channel update") // This time a valid signature was supplied and the policy change should @@ -1428,7 +1431,7 @@ func TestSendToRouteStructuredError(t *testing.T) { // update should be received by router and ignored // because it is missing a valid // signature. - _, err = ctx.router.SendToRoute(payment, rt) + _, err = ctx.router.SendToRoute(payment, rt, nil) fErr, ok := err.(*htlcswitch.ForwardingError) require.True( @@ -1507,7 +1510,7 @@ func TestSendToRouteMaxHops(t *testing.T) { // Send off the payment request to the router. We expect an error back // indicating that the route is too long. var payHash lntypes.Hash - _, err = ctx.router.SendToRoute(payHash, rt) + _, err = ctx.router.SendToRoute(payHash, rt, nil) if err != route.ErrMaxRouteHopsExceeded { t.Fatalf("expected ErrMaxRouteHopsExceeded, but got %v", err) } @@ -1649,12 +1652,16 @@ func TestBuildRoute(t *testing.T) { // Test that we can't build a route when no hops are given. hops = []route.Vertex{} - _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil) + _, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](), + ) require.Error(t, err) // Create hop list for an unknown destination. hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]} - _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) + _, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](), + ) noChanErr := ErrNoChannel{} require.ErrorAs(t, err, &noChanErr) require.Equal(t, 1, noChanErr.position) @@ -1664,7 +1671,10 @@ func TestBuildRoute(t *testing.T) { amt := lnwire.NewMSatFromSatoshis(100) // Build the route for the given amount. - rt, err := ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr) + rt, err := ctx.router.BuildRoute( + fn.Some(amt), hops, nil, 40, fn.Some(payAddr), + fn.None[[]byte](), + ) require.NoError(t, err) // Check that we get the expected route back. The total amount should be @@ -1674,7 +1684,9 @@ func TestBuildRoute(t *testing.T) { require.Equal(t, lnwire.MilliSatoshi(106000), rt.TotalAmount) // Build the route for the minimum amount. - rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](), + ) require.NoError(t, err) // Check that we get the expected route back. The minimum that we can @@ -1690,7 +1702,9 @@ func TestBuildRoute(t *testing.T) { // Test a route that contains incompatible channel htlc constraints. // There is no amount that can pass through both channel 5 and 4. hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]} - _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil) + _, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](), + ) require.Error(t, err) noChanErr = ErrNoChannel{} require.ErrorAs(t, err, &noChanErr) @@ -1708,7 +1722,9 @@ func TestBuildRoute(t *testing.T) { // amount that could be delivered to the receiver of 21819 msat, using // policy of channel 3. hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]} - rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](), + ) require.NoError(t, err) checkHops(rt, []uint64{1, 8}, payAddr) require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount) @@ -1720,7 +1736,10 @@ func TestBuildRoute(t *testing.T) { // We get 106000 - 1000 (base in) - 0.001 * 106000 (rate in) = 104894. hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]} amt = lnwire.NewMSatFromSatoshis(100) - rt, err = ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute( + fn.Some(amt), hops, nil, 40, fn.Some(payAddr), + fn.None[[]byte](), + ) require.NoError(t, err) checkHops(rt, []uint64{9, 10}, payAddr) require.EqualValues(t, 104894, rt.TotalAmount) @@ -1734,7 +1753,9 @@ func TestBuildRoute(t *testing.T) { // of 20179 msat, which results in underpayment of 1 msat in fee. There // is a third pass through newRoute in which this gets corrected to end hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]} - rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute( + noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](), + ) require.NoError(t, err) checkHops(rt, []uint64{9, 10}, payAddr) require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount) @@ -2173,7 +2194,8 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) { NextPaymentID: func() (uint64, error) { return 0, nil }, - ClosedSCIDs: mockClosedSCIDs, + ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), }} // Register mockers with the expected method calls. @@ -2208,7 +2230,7 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) { payment.On("TerminalInfo").Return(nil, nil) // Expect a successful send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.NoError(t, err) require.Equal(t, testAttempt, attempt) @@ -2257,11 +2279,12 @@ func TestSendToRouteSkipTempErrNonMPP(t *testing.T) { NextPaymentID: func() (uint64, error) { return 0, nil }, - ClosedSCIDs: mockClosedSCIDs, + ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), }} // Expect an error to be returned. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.ErrorIs(t, ErrSkipTempErr, err) require.Nil(t, attempt) @@ -2312,7 +2335,8 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) { NextPaymentID: func() (uint64, error) { return 0, nil }, - ClosedSCIDs: mockClosedSCIDs, + ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), }} // Create the error to be returned. @@ -2345,7 +2369,7 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) { payment.On("TerminalInfo").Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) @@ -2395,7 +2419,8 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) { NextPaymentID: func() (uint64, error) { return 0, nil }, - ClosedSCIDs: mockClosedSCIDs, + ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), }} // Create the error to be returned. @@ -2432,7 +2457,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) { payment.On("TerminalInfo").Return(nil, &failureReason) // Expect a failed send to route. - attempt, err := router.SendToRouteSkipTempErr(payHash, rt) + attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil) require.Equal(t, permErr, err) require.Equal(t, testAttempt, attempt) @@ -2482,7 +2507,8 @@ func TestSendToRouteTempFailure(t *testing.T) { NextPaymentID: func() (uint64, error) { return 0, nil }, - ClosedSCIDs: mockClosedSCIDs, + ClosedSCIDs: mockClosedSCIDs, + TrafficShaper: fn.Some[TlvTrafficShaper](&mockTrafficShaper{}), }} // Create the error to be returned. @@ -2517,7 +2543,7 @@ func TestSendToRouteTempFailure(t *testing.T) { ).Return(nil, nil) // Expect a failed send to route. - attempt, err := router.SendToRoute(payHash, rt) + attempt, err := router.SendToRoute(payHash, rt, nil) require.Equal(t, tempErr, err) require.Equal(t, testAttempt, attempt) diff --git a/routing/unified_edges.go b/routing/unified_edges.go index a0300eea4b..6c44372e99 100644 --- a/routing/unified_edges.go +++ b/routing/unified_edges.go @@ -247,8 +247,13 @@ func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi, // local channel. amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee) - // Check valid amount range for the channel. - if !edge.amtInRange(amt) { + // Check valid amount range for the channel. We skip this test + // for payments with custom HTLC data, as the amount sent on + // the BTC layer may differ from the amount that is actually + // forwarded in custom channels. + if bandwidthHints.firstHopCustomBlob().IsNone() && + !edge.amtInRange(amt) { + log.Debugf("Amount %v not in range for edge %v", netAmtReceived, edge.policy.ChannelID) diff --git a/rpcserver.go b/rpcserver.go index 7fef873266..8ed33d95af 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -87,6 +87,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "gopkg.in/macaroon-bakery.v2/bakery" ) @@ -579,6 +580,17 @@ func MainRPCServerPermissions() map[string][]bakery.Op { } } +// AuxDataParser is an interface that is used to parse auxiliary custom data +// within RPC messages. This is used to transform binary blobs to human-readable +// JSON representations. +type AuxDataParser interface { + // InlineParseCustomData replaces any custom data binary blob in the + // given RPC message with its corresponding JSON formatted data. This + // transforms the binary (likely TLV encoded) data to a human-readable + // JSON representation (still as byte slice). + InlineParseCustomData(msg proto.Message) error +} + // rpcServer is a gRPC, RPC front end to the lnd daemon. // TODO(roasbeef): pagination support for the list-style calls type rpcServer struct { @@ -672,7 +684,8 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain, func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone, - chanPredicate chanacceptor.MultiplexAcceptor) error { + chanPredicate chanacceptor.MultiplexAcceptor, + invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error { // Set up router rpc backend. selfNode, err := s.graphDB.SourceNode() @@ -731,6 +744,20 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, }, SetChannelAuto: s.chanStatusMgr.RequestAuto, UseStatusInitiated: subServerCgs.RouterRPC.UseStatusInitiated, + ParseCustomChannelData: func(msg proto.Message) error { + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData(msg) + }, + ) + if err != nil { + return fmt.Errorf("error parsing custom data: "+ + "%w", err) + } + + return nil + }, } genInvoiceFeatures := func() *lnwire.FeatureVector { @@ -761,7 +788,8 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service, s.sweeper, tower, s.towerClientMgr, r.cfg.net.ResolveTCPAddr, genInvoiceFeatures, genAmpInvoiceFeatures, s.getNodeAnnouncement, s.updateAndBrodcastSelfNode, parseAddr, - rpcsLog, s.aliasMgr.GetPeerAlias, + rpcsLog, s.aliasMgr, r.implCfg.AuxDataParser, + invoiceHtlcModifier, ) if err != nil { return err @@ -2281,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) @@ -2657,7 +2708,6 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // transaction here rather than going to the switch as we don't require // interaction from the peer. if force { - // As we're force closing this channel, as a precaution, we'll // ensure that the switch doesn't continue to see this channel // as eligible for forwarding HTLC's. If the peer is online, @@ -2686,6 +2736,14 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, return err } + // Safety check which should never happen. + // + // TODO(ziggie): remove pointer as return value from + // ForceCloseContract. + if closingTx == nil { + return fmt.Errorf("force close transaction is nil") + } + closingTxid := closingTx.TxHash() // With the transaction broadcast, we send our first update to @@ -2697,15 +2755,19 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, errChan = make(chan error, 1) notifier := r.server.cc.ChainNotifier - go peer.WaitForChanToClose(uint32(bestHeight), notifier, errChan, chanPoint, + go peer.WaitForChanToClose( + uint32(bestHeight), notifier, errChan, chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which // requested the channel closure. updateChan <- &peer.ChannelCloseUpdate{ ClosingTxid: closingTxid[:], Success: true, + // Force closure transactions don't have + // additional local/remote outputs. } - }) + }, + ) } else { // If this is a frozen channel, then we only allow the co-op // close to proceed if we were the responder to this channel if @@ -2829,6 +2891,19 @@ out: return err } + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData( + rpcClosingUpdate, + ) + }, + ) + if err != nil { + return fmt.Errorf("error parsing custom data: "+ + "%w", err) + } + rpcsLog.Tracef("[closechannel] sending update: %v", rpcClosingUpdate) @@ -2854,19 +2929,84 @@ out: return nil } -func createRPCCloseUpdate(update interface{}) ( - *lnrpc.CloseStatusUpdate, error) { +func createRPCCloseUpdate( + update interface{}) (*lnrpc.CloseStatusUpdate, error) { switch u := update.(type) { case *peer.ChannelCloseUpdate: + ccu := &lnrpc.ChannelCloseUpdate{ + ClosingTxid: u.ClosingTxid, + Success: u.Success, + } + + err := fn.MapOptionZ( + u.LocalCloseOutput, + func(closeOut chancloser.CloseOutput) error { + cr, err := closeOut.ShutdownRecords.Serialize() + if err != nil { + return fmt.Errorf("error serializing "+ + "local close out custom "+ + "records: %w", err) + } + + rpcCloseOut := &lnrpc.CloseOutput{ + AmountSat: int64(closeOut.Amt), + PkScript: closeOut.PkScript, + IsLocal: true, + CustomChannelData: cr, + } + ccu.LocalCloseOutput = rpcCloseOut + + return nil + }, + ) + if err != nil { + return nil, err + } + + err = fn.MapOptionZ( + u.RemoteCloseOutput, + func(closeOut chancloser.CloseOutput) error { + cr, err := closeOut.ShutdownRecords.Serialize() + if err != nil { + return fmt.Errorf("error serializing "+ + "remote close out custom "+ + "records: %w", err) + } + + rpcCloseOut := &lnrpc.CloseOutput{ + AmountSat: int64(closeOut.Amt), + PkScript: closeOut.PkScript, + CustomChannelData: cr, + } + ccu.RemoteCloseOutput = rpcCloseOut + + return nil + }, + ) + if err != nil { + return nil, err + } + + u.AuxOutputs.WhenSome(func(outs chancloser.AuxCloseOutputs) { + for _, out := range outs.ExtraCloseOutputs { + ccu.AdditionalOutputs = append( + ccu.AdditionalOutputs, + &lnrpc.CloseOutput{ + AmountSat: out.Value, + PkScript: out.PkScript, + IsLocal: out.IsLocal, + }, + ) + } + }) + return &lnrpc.CloseStatusUpdate{ Update: &lnrpc.CloseStatusUpdate_ChanClose{ - ChanClose: &lnrpc.ChannelCloseUpdate{ - ClosingTxid: u.ClosingTxid, - Success: u.Success, - }, + ChanClose: ccu, }, }, nil + case *peer.PendingUpdate: return &lnrpc.CloseStatusUpdate{ Update: &lnrpc.CloseStatusUpdate_ClosePending{ @@ -3536,6 +3676,7 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, unsettledRemoteBalance lnwire.MilliSatoshi pendingOpenLocalBalance lnwire.MilliSatoshi pendingOpenRemoteBalance lnwire.MilliSatoshi + customDataBuf bytes.Buffer ) openChannels, err := r.server.chanStateDB.FetchAllOpenChannels() @@ -3543,6 +3684,12 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, return nil, err } + // Encode the number of open channels to the custom data buffer. + err = wire.WriteVarInt(&customDataBuf, 0, uint64(len(openChannels))) + if err != nil { + return nil, err + } + for _, channel := range openChannels { c := channel.LocalCommitment localBalance += c.LocalBalance @@ -3556,6 +3703,13 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, unsettledRemoteBalance += htlc.Amt } } + + // Encode the custom data for this open channel. + openChanData := channel.LocalCommitment.CustomBlob.UnwrapOr(nil) + err = wire.WriteVarBytes(&customDataBuf, 0, openChanData) + if err != nil { + return nil, err + } } pendingChannels, err := r.server.chanStateDB.FetchPendingChannels() @@ -3563,10 +3717,23 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, return nil, err } + // Encode the number of pending channels to the custom data buffer. + err = wire.WriteVarInt(&customDataBuf, 0, uint64(len(pendingChannels))) + if err != nil { + return nil, err + } + for _, channel := range pendingChannels { c := channel.LocalCommitment pendingOpenLocalBalance += c.LocalBalance pendingOpenRemoteBalance += c.RemoteBalance + + // Encode the custom data for this pending channel. + openChanData := channel.LocalCommitment.CustomBlob.UnwrapOr(nil) + err = wire.WriteVarBytes(&customDataBuf, 0, openChanData) + if err != nil { + return nil, err + } } rpcsLog.Debugf("[channelbalance] local_balance=%v remote_balance=%v "+ @@ -3576,7 +3743,7 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, unsettledRemoteBalance, pendingOpenLocalBalance, pendingOpenRemoteBalance) - return &lnrpc.ChannelBalanceResponse{ + resp := &lnrpc.ChannelBalanceResponse{ LocalBalance: &lnrpc.Amount{ Sat: uint64(localBalance.ToSatoshis()), Msat: uint64(localBalance), @@ -3601,11 +3768,24 @@ func (r *rpcServer) ChannelBalance(ctx context.Context, Sat: uint64(pendingOpenRemoteBalance.ToSatoshis()), Msat: uint64(pendingOpenRemoteBalance), }, + CustomChannelData: customDataBuf.Bytes(), // Deprecated fields. Balance: int64(localBalance.ToSatoshis()), PendingOpenBalance: int64(pendingOpenLocalBalance.ToSatoshis()), - }, nil + } + + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData(resp) + }, + ) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", err) + } + + return resp, nil } type ( @@ -3661,6 +3841,12 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) { pendingChan.BroadcastHeight() fundingExpiryBlocks := int32(maxFundingHeight) - currentHeight + customChanBytes, err := encodeCustomChanData(pendingChan) + if err != nil { + return nil, fmt.Errorf("unable to encode open chan "+ + "data: %w", err) + } + result[i] = &lnrpc.PendingChannelsResponse_PendingOpenChannel{ Channel: &lnrpc.PendingChannelsResponse_PendingChannel{ RemoteNodePub: hex.EncodeToString(pub), @@ -3674,6 +3860,7 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) { CommitmentType: rpcCommitmentType(pendingChan.ChanType), Private: isPrivate(pendingChan), Memo: string(pendingChan.Memo), + CustomChannelData: customChanBytes, }, CommitWeight: commitWeight, CommitFee: int64(localCommitment.CommitFee), @@ -4040,6 +4227,16 @@ func (r *rpcServer) PendingChannels(ctx context.Context, resp.WaitingCloseChannels = waitingCloseChannels resp.TotalLimboBalance += limbo + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData(resp) + }, + ) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", err) + } + return resp, nil } @@ -4354,6 +4551,16 @@ func (r *rpcServer) ListChannels(ctx context.Context, resp.Channels = append(resp.Channels, channel) } + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData(resp) + }, + ) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", err) + } + return resp, nil } @@ -4364,6 +4571,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 @@ -4375,6 +4585,7 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { case chanType.IsTweakless(): return lnrpc.CommitmentType_STATIC_REMOTE_KEY + default: return lnrpc.CommitmentType_LEGACY @@ -4404,6 +4615,35 @@ func isPrivate(dbChannel *channeldb.OpenChannel) bool { return dbChannel.ChannelFlags&lnwire.FFAnnounceChannel != 1 } +// encodeCustomChanData encodes the custom channel data for the open channel. +// It encodes that data as a pair of var bytes blobs. +func encodeCustomChanData(lnChan *channeldb.OpenChannel) ([]byte, error) { + customOpenChanData := lnChan.CustomBlob.UnwrapOr(nil) + customLocalCommitData := lnChan.LocalCommitment.CustomBlob.UnwrapOr(nil) + + // Don't write any custom data if both blobs are empty. + if len(customOpenChanData) == 0 && len(customLocalCommitData) == 0 { + return nil, nil + } + + // We'll encode our custom channel data as two blobs. The first is a + // set of var bytes encoding of the open chan data, the second is an + // encoding of the local commitment data. + var customChanDataBuf bytes.Buffer + err := wire.WriteVarBytes(&customChanDataBuf, 0, customOpenChanData) + if err != nil { + return nil, fmt.Errorf("unable to encode open chan "+ + "data: %w", err) + } + err = wire.WriteVarBytes(&customChanDataBuf, 0, customLocalCommitData) + if err != nil { + return nil, fmt.Errorf("unable to encode local commit "+ + "data: %w", err) + } + + return customChanDataBuf.Bytes(), nil +} + // createRPCOpenChannel creates an *lnrpc.Channel from the *channeldb.Channel. func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel, isActive, peerAliasLookup bool) (*lnrpc.Channel, error) { @@ -4458,6 +4698,14 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel, // is returned and peerScidAlias will be an empty ShortChannelID. peerScidAlias, _ := r.server.aliasMgr.GetPeerAlias(chanID) + // Finally we'll attempt to encode the custom channel data if any + // exists. + customChanBytes, err := encodeCustomChanData(dbChannel) + if err != nil { + return nil, fmt.Errorf("unable to encode open chan data: %w", + err) + } + channel := &lnrpc.Channel{ Active: isActive, Private: isPrivate(dbChannel), @@ -4490,6 +4738,7 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel, ZeroConf: dbChannel.IsZeroConf(), ZeroConfConfirmedScid: dbChannel.ZeroConfRealScid().ToUint64(), Memo: string(dbChannel.Memo), + CustomChannelData: customChanBytes, // TODO: remove the following deprecated fields CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay), LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve), @@ -5119,7 +5368,7 @@ type rpcPaymentIntent struct { outgoingChannelIDs []uint64 lastHop *route.Vertex destFeatures *lnwire.FeatureVector - paymentAddr *[32]byte + paymentAddr fn.Option[[32]byte] payReq []byte metadata []byte blindedPathSet *routing.BlindedPaymentPathSet @@ -5328,8 +5577,9 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme // Note that the payment address for the payIntent should be nil if none // was provided with the rpcPaymentRequest. if len(rpcPayReq.PaymentAddr) != 0 { - payIntent.paymentAddr = &[32]byte{} - copy(payIntent.paymentAddr[:], rpcPayReq.PaymentAddr) + var addr [32]byte + copy(addr[:], rpcPayReq.PaymentAddr) + payIntent.paymentAddr = fn.Some(addr) } // Otherwise, If the payment request field was not specified @@ -5452,7 +5702,7 @@ func (r *rpcServer) dispatchPaymentIntent( } else { var attempt *channeldb.HTLCAttempt attempt, routerErr = r.server.chanRouter.SendToRoute( - payIntent.rHash, payIntent.route, + payIntent.rHash, payIntent.route, nil, ) if routerErr == nil { @@ -5997,6 +6247,19 @@ func (r *rpcServer) LookupInvoice(ctx context.Context, return nil, err } + // Give the aux data parser a chance to format the custom data in the + // invoice HTLCs. + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData(rpcInvoice) + }, + ) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", + err) + } + return rpcInvoice, nil } @@ -6052,6 +6315,21 @@ func (r *rpcServer) ListInvoices(ctx context.Context, if err != nil { return nil, err } + + // Give the aux data parser a chance to format the custom data + // in the invoice HTLCs. + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData( + resp.Invoices[i], + ) + }, + ) + if err != nil { + return nil, fmt.Errorf("error parsing custom data: %w", + err) + } } return resp, nil @@ -6080,6 +6358,21 @@ func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription, return err } + // Give the aux data parser a chance to format the + // custom data in the invoice HTLCs. + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData( + rpcInvoice, + ) + }, + ) + if err != nil { + return fmt.Errorf("error parsing custom data: "+ + "%w", err) + } + if err := updateStream.Send(rpcInvoice); err != nil { return err } @@ -6092,6 +6385,21 @@ func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription, return err } + // Give the aux data parser a chance to format the + // custom data in the invoice HTLCs. + err = fn.MapOptionZ( + r.server.implCfg.AuxDataParser, + func(parser AuxDataParser) error { + return parser.InlineParseCustomData( + rpcInvoice, + ) + }, + ) + if err != nil { + return fmt.Errorf("error parsing custom data: "+ + "%w", err) + } + if err := updateStream.Send(rpcInvoice); err != nil { return err } @@ -7117,10 +7425,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context, } // Extract the payment address from the payment request, if present. - var paymentAddr []byte - if payReq.PaymentAddr != nil { - paymentAddr = payReq.PaymentAddr[:] - } + paymentAddr := payReq.PaymentAddr.UnwrapOr([32]byte{}) dest := payReq.Destination.SerializeCompressed() return &lnrpc.PayReq{ @@ -7136,7 +7441,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context, CltvExpiry: int64(payReq.MinFinalCLTVExpiry()), RouteHints: routeHints, BlindedPaths: blindedPaymentPaths, - PaymentAddr: paymentAddr, + PaymentAddr: paymentAddr[:], Features: invoicesrpc.CreateRPCFeatures(payReq.Features), }, nil } @@ -8548,17 +8853,8 @@ func (r *rpcServer) ListAliases(ctx context.Context, AliasMaps: make([]*lnrpc.AliasMap, 0), } - for base, set := range mapAliases { - rpcMap := &lnrpc.AliasMap{ - BaseScid: base.ToUint64(), - } - for _, alias := range set { - rpcMap.Aliases = append( - rpcMap.Aliases, alias.ToUint64(), - ) - } - resp.AliasMaps = append(resp.AliasMaps, rpcMap) - } + // Now we need to parse the created mappings into an rpc response. + resp.AliasMaps = lnrpc.MarshalAliasMap(mapAliases) return resp, nil } diff --git a/rpcserver_test.go b/rpcserver_test.go index ca70ad9df7..53ec6d0ac3 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -1,14 +1,129 @@ package lnd import ( + "fmt" "testing" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) func TestGetAllPermissions(t *testing.T) { perms := GetAllPermissions() - // Currently there are there are 16 entity:action pairs in use. + // Currently there are 16 entity:action pairs in use. assert.Equal(t, len(perms), 16) } + +// mockDataParser is a mock implementation of the AuxDataParser interface. +type mockDataParser struct { +} + +// InlineParseCustomData replaces any custom data binary blob in the given RPC +// message with its corresponding JSON formatted data. This transforms the +// binary (likely TLV encoded) data to a human-readable JSON representation +// (still as byte slice). +func (m *mockDataParser) InlineParseCustomData(msg proto.Message) error { + switch m := msg.(type) { + case *lnrpc.ChannelBalanceResponse: + m.CustomChannelData = []byte(`{"foo": "bar"}`) + + return nil + + default: + return fmt.Errorf("mock only supports ChannelBalanceResponse") + } +} + +func TestAuxDataParser(t *testing.T) { + // We create an empty channeldb, so we can fetch some channels. + cdb, err := channeldb.Open(t.TempDir()) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, cdb.Close()) + }) + + r := &rpcServer{ + server: &server{ + chanStateDB: cdb.ChannelStateDB(), + implCfg: &ImplementationCfg{ + AuxComponents: AuxComponents{ + AuxDataParser: fn.Some[AuxDataParser]( + &mockDataParser{}, + ), + }, + }, + }, + } + + // With the aux data parser in place, we should get a formatted JSON + // in the custom channel data field. + resp, err := r.ChannelBalance(nil, &lnrpc.ChannelBalanceRequest{}) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, []byte(`{"foo": "bar"}`), resp.CustomChannelData) + + // If we don't supply the aux data parser, we should get the raw binary + // data. Which in this case is just two VarInt fields (1 byte each) that + // represent the value of 0 (zero active and zero pending channels). + r.server.implCfg.AuxComponents.AuxDataParser = fn.None[AuxDataParser]() + + resp, err = r.ChannelBalance(nil, &lnrpc.ChannelBalanceRequest{}) + require.NoError(t, err) + 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 c9fb0e4f8c..865b5310fd 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -1321,6 +1321,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/scripts/install_bitcoind.sh b/scripts/install_bitcoind.sh index b4faa4becd..8f74efa46a 100755 --- a/scripts/install_bitcoind.sh +++ b/scripts/install_bitcoind.sh @@ -4,13 +4,20 @@ set -ev BITCOIND_VERSION=$1 +# Useful for testing RCs: e.g. TAG_SUFFIX=.0rc1, DIR_SUFFIX=.0rc1 +TAG_SUFFIX= +DIR_SUFFIX=.0 + +# Useful for testing against an image pushed to a different Docker repo. +REPO=lightninglabs/bitcoin-core + if [ -z "$BITCOIND_VERSION" ]; then echo "Must specify a version of bitcoind to install." echo "Usage: install_bitcoind.sh " exit 1 fi -docker pull lightninglabs/bitcoin-core:${BITCOIND_VERSION} -CONTAINER_ID=$(docker create lightninglabs/bitcoin-core:${BITCOIND_VERSION}) -sudo docker cp $CONTAINER_ID:/opt/bitcoin-${BITCOIND_VERSION}.0/bin/bitcoind /usr/local/bin/bitcoind +docker pull ${REPO}:${BITCOIND_VERSION}${TAG_SUFFIX} +CONTAINER_ID=$(docker create ${REPO}:${BITCOIND_VERSION}${TAG_SUFFIX}) +sudo docker cp $CONTAINER_ID:/opt/bitcoin-${BITCOIND_VERSION}${DIR_SUFFIX}/bin/bitcoind /usr/local/bin/bitcoind docker rm $CONTAINER_ID diff --git a/server.go b/server.go index 33f1098484..3dd8040e0a 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" @@ -160,6 +161,8 @@ type server struct { cfg *Config + implCfg *ImplementationCfg + // identityECDH is an ECDH capable wrapper for the private key used // to authenticate any incoming connections. identityECDH keychain.SingleKeyECDH @@ -261,6 +264,8 @@ type server struct { invoices *invoices.InvoiceRegistry + invoiceHtlcModifier *invoices.HtlcModificationInterceptor + channelNotifier *channelnotifier.ChannelNotifier peerNotifier *peernotifier.PeerNotifier @@ -486,7 +491,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chansToRestore walletunlocker.ChannelsToRecover, chanPredicate chanacceptor.ChannelAcceptor, torController *tor.Controller, tlsManager *TLSManager, - leaderElector cluster.LeaderElector) (*server, error) { + leaderElector cluster.LeaderElector, + implCfg *ImplementationCfg) (*server, error) { var ( err error @@ -515,6 +521,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, @@ -539,6 +547,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(), @@ -552,12 +569,14 @@ 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 { return nil, err } + invoiceHtlcModifier := invoices.NewHtlcModificationInterceptor() registryConfig := invoices.RegistryConfig{ FinalCltvRejectDelta: lncfg.DefaultFinalCltvRejectDelta, HtlcHoldDuration: invoices.DefaultHtlcHoldDuration, @@ -567,10 +586,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, GcCanceledInvoicesOnStartup: cfg.GcCanceledInvoicesOnStartup, GcCanceledInvoicesOnTheFly: cfg.GcCanceledInvoicesOnTheFly, KeysendHoldTime: cfg.KeysendHoldTime, + HtlcInterceptor: invoiceHtlcModifier, } s := &server{ cfg: cfg, + implCfg: implCfg, graphDB: dbs.GraphDB.ChannelGraph(), chanStateDB: dbs.ChanStateDB.ChannelStateDB(), addrSource: dbs.ChanStateDB, @@ -614,6 +635,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, peerConnectedListeners: make(map[string][]chan<- lnpeer.Peer), peerDisconnectedListeners: make(map[string][]chan<- struct{}), + invoiceHtlcModifier: invoiceHtlcModifier, + customMessageServer: subscribe.NewServer(), tlsManager: tlsManager, @@ -640,7 +663,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr, thresholdSats := btcutil.Amount(cfg.MaxFeeExposure) thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats) - s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB) + linkUpdater := func(shortID lnwire.ShortChannelID) error { + link, err := s.htlcSwitch.GetLinkByShortID(shortID) + if err != nil { + return err + } + + s.htlcSwitch.UpdateLinkAliases(link) + + return nil + } + + s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB, linkUpdater) if err != nil { return nil, err } @@ -1006,6 +1040,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, Clock: clock.NewDefaultClock(), ApplyChannelUpdate: s.graphBuilder.ApplyChannelUpdate, ClosedSCIDs: s.fetchClosedChannelSCIDs(), + TrafficShaper: implCfg.TrafficShaper, }) if err != nil { return nil, fmt.Errorf("can't create router: %w", err) @@ -1089,18 +1124,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, @@ -1143,10 +1182,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, @@ -1154,6 +1195,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, Store: contractcourt.NewRetributionStore( dbs.ChanStateDB, ), + AuxSweeper: s.implCfg.AuxSweeper, }, ) @@ -1162,8 +1204,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) @@ -1268,6 +1319,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return &pc.Incoming }, + AuxLeafStore: implCfg.AuxLeafStore, + AuxSigner: implCfg.AuxSigner, + AuxResolver: implCfg.AuxContractResolver, }, dbs.ChanStateDB) // Select the configuration and funding parameters for Bitcoin. @@ -1512,9 +1566,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr, EnableUpfrontShutdown: cfg.EnableUpfrontShutdown, MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte( s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(), - DeleteAliasEdge: deleteAliasEdge, - AliasManager: s.aliasMgr, - IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint, + DeleteAliasEdge: deleteAliasEdge, + AliasManager: s.aliasMgr, + IsSweeperOutpoint: s.sweeper.IsSweeperOutpoint, + AuxFundingController: implCfg.AuxFundingController, + AuxSigner: implCfg.AuxSigner, + AuxResolver: implCfg.AuxContractResolver, }) if err != nil { return nil, err @@ -1602,6 +1659,8 @@ 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 @@ -1635,8 +1694,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, @@ -2068,6 +2136,12 @@ func (s *server) Start() error { return } + cleanup = cleanup.add(s.invoiceHtlcModifier.Stop) + if err := s.invoiceHtlcModifier.Start(); err != nil { + startErr = err + return + } + cleanup = cleanup.add(s.chainArb.Stop) if err := s.chainArb.Start(); err != nil { startErr = err @@ -2347,6 +2421,14 @@ func (s *server) Stop() error { if err := s.invoices.Stop(); err != nil { srvrLog.Warnf("failed to stop invoices: %v", err) } + if err := s.interceptableSwitch.Stop(); err != nil { + srvrLog.Warnf("failed to stop interceptable "+ + "switch: %v", err) + } + if err := s.invoiceHtlcModifier.Stop(); err != nil { + srvrLog.Warnf("failed to stop htlc invoices "+ + "modifier: %v", err) + } if err := s.chanRouter.Stop(); err != nil { srvrLog.Warnf("failed to stop chanRouter: %v", err) } @@ -4039,6 +4121,11 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(), MaxFeeExposure: thresholdMSats, Quit: s.quit, + AuxLeafStore: s.implCfg.AuxLeafStore, + AuxSigner: s.implCfg.AuxSigner, + MsgRouter: s.implCfg.MsgRouter, + AuxChanCloser: s.implCfg.AuxChanCloser, + AuxResolver: s.implCfg.AuxContractResolver, } copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed()) @@ -4131,11 +4218,12 @@ func (s *server) peerInitializer(p *peer.Brontide) { s.wg.Add(1) go s.peerTerminationWatcher(p, ready) + pubBytes := p.IdentityKey().SerializeCompressed() + // Start the peer! If an error occurs, we Disconnect the peer, which // will unblock the peerTerminationWatcher. if err := p.Start(); err != nil { - srvrLog.Warnf("Starting peer=%v got error: %v", - p.IdentityKey(), err) + srvrLog.Warnf("Starting peer=%x got error: %v", pubBytes, err) p.Disconnect(fmt.Errorf("unable to start peer: %w", err)) return @@ -4145,13 +4233,15 @@ func (s *server) peerInitializer(p *peer.Brontide) { // was successful, and to begin watching the peer's wait group. close(ready) - pubStr := string(p.IdentityKey().SerializeCompressed()) - s.mu.Lock() defer s.mu.Unlock() // Check if there are listeners waiting for this peer to come online. srvrLog.Debugf("Notifying that peer %v is online", p) + + // TODO(guggero): Do a proper conversion to a string everywhere, or use + // route.Vertex as the key type of peerConnectedListeners. + pubStr := string(pubBytes) for _, peerChan := range s.peerConnectedListeners[pubStr] { select { case peerChan <- p: @@ -4855,18 +4945,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) + } + + addr, err := txscript.PayToAddrScript(sweepAddr) + if err != nil { + return fn.Err[lnwallet.AddrWithKey](err) } - return txscript.PayToAddrScript(sweepAddr) + 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/subrpcserver_config.go b/subrpcserver_config.go index 6687f71a7d..5328c4919e 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -7,9 +7,11 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/aliasmgr" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lncfg" @@ -31,6 +33,7 @@ import ( "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/watchtower" "github.com/lightningnetwork/lnd/watchtower/wtclient" + "google.golang.org/protobuf/proto" ) // subRPCServerConfigs is special sub-config in the main configuration that @@ -121,8 +124,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, updateNodeAnnouncement func(features *lnwire.RawFeatureVector, modifiers ...netann.NodeAnnModifier) error, parseAddr func(addr string) (net.Addr, error), - rpcLogger btclog.Logger, - getAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)) error { + rpcLogger btclog.Logger, aliasMgr *aliasmgr.Manager, + auxDataParser fn.Option[AuxDataParser], + invoiceHtlcModifier *invoices.HtlcModificationInterceptor) error { // First, we'll use reflect to obtain a version of the config struct // that allows us to programmatically inspect its fields. @@ -238,6 +242,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, subCfgValue.FieldByName("InvoiceRegistry").Set( reflect.ValueOf(invoiceRegistry), ) + subCfgValue.FieldByName("HtlcModifier").Set( + reflect.ValueOf(invoiceHtlcModifier), + ) subCfgValue.FieldByName("IsChannelActive").Set( reflect.ValueOf(htlcSwitch.HasActiveLink), ) @@ -264,7 +271,21 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, reflect.ValueOf(genAmpInvoiceFeatures), ) subCfgValue.FieldByName("GetAlias").Set( - reflect.ValueOf(getAlias), + reflect.ValueOf(aliasMgr.GetPeerAlias), + ) + + parseAuxData := func(m proto.Message) error { + return fn.MapOptionZ( + auxDataParser, + func(p AuxDataParser) error { + return p.InlineParseCustomData( + m, + ) + }, + ) + } + subCfgValue.FieldByName("ParseAuxData").Set( + reflect.ValueOf(parseAuxData), ) case *neutrinorpc.Config: @@ -277,6 +298,11 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config, // RouterRPC isn't conditionally compiled and doesn't need to be // populated using reflection. case *routerrpc.Config: + subCfgValue := extractReflectValue(subCfg) + + subCfgValue.FieldByName("AliasMgr").Set( + reflect.ValueOf(aliasMgr), + ) case *watchtowerrpc.Config: subCfgValue := extractReflectValue(subCfg) diff --git a/sweep/aggregator.go b/sweep/aggregator.go index 6028adca3e..cd809e81a9 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 a32c849a03..6df0d73fa2 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 1d3fa5aedd..adb4db65ed 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,19 @@ 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, + sweepCtx.outpointToTxIndex, + ) 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 +471,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 +484,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 @@ -461,7 +511,8 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest, // storeRecord stores the given record in the records map. func (t *TxPublisher) storeRecord(tx *wire.MsgTx, req *BumpRequest, - f FeeFunction, fee btcutil.Amount) uint64 { + f FeeFunction, fee btcutil.Amount, + outpointToTxIndex map[wire.OutPoint]int) uint64 { // Increase the request counter. // @@ -471,10 +522,11 @@ func (t *TxPublisher) storeRecord(tx *wire.MsgTx, req *BumpRequest, // Register the record. t.records.Store(requestID, &monitorRecord{ - tx: tx, - req: req, - feeFunction: f, - fee: fee, + tx: tx, + req: req, + feeFunction: f, + fee: fee, + outpointToTxIndex: outpointToTxIndex, }) return requestID @@ -484,30 +536,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 +571,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 +603,17 @@ 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, record.outpointToTxIndex, + ) + }) + 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 +621,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 { @@ -663,6 +730,9 @@ type monitorRecord struct { // fee is the fee paid by the tx. fee btcutil.Amount + + // outpointToTxIndex is a map of outpoint to tx index. + outpointToTxIndex map[wire.OutPoint]int } // Start starts the publisher by subscribing to block epoch updates and kicking @@ -933,7 +1003,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 +1050,18 @@ 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, - req: r.req, - feeFunction: r.feeFunction, - fee: fee, + tx: sweepCtx.tx, + req: r.req, + feeFunction: r.feeFunction, + fee: sweepCtx.fee, + outpointToTxIndex: sweepCtx.outpointToTxIndex, }) // 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 +1087,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 +1201,32 @@ 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] + + // outpointToTxIndex maps the outpoint of the inputs to their index in + // the sweep transaction. + outpointToTxIndex map[wire.OutPoint]int +} + // 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 ( @@ -1155,6 +1242,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, // We start by adding all inputs that commit to an output. We do this // since the input and output index must stay the same for the // signatures to be valid. + outpointToTxIndex := make(map[wire.OutPoint]int) for _, o := range inputs { if o.RequiredTxOut() == nil { continue @@ -1166,6 +1254,8 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte, Sequence: o.BlocksToMaturity(), }) sweepTx.AddTxOut(o.RequiredTxOut()) + + outpointToTxIndex[o.OutPoint()] = len(sweepTx.TxOut) - 1 } // Sum up the value contained in the remaining inputs, and add them to @@ -1182,12 +1272,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 +1286,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 +1314,88 @@ 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), + outpointToTxIndex: outpointToTxIndex, + }, 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 +1413,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 +1465,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 +1495,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 +1513,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 +1525,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 4c4a9519fe..5030dee227 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,13 +316,23 @@ 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() + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + // Call the method under test. - requestID := tp.storeRecord(tx, req, feeFunc, fee) + requestID := tp.storeRecord(tx, req, feeFunc, fee, utxoIndex) // Check the request ID is as expected. require.Equal(t, initialCounter+1, requestID) @@ -322,6 +344,7 @@ func TestStoreRecord(t *testing.T) { require.Equal(t, feeFunc, record.feeFunction) require.Equal(t, fee, record.fee) require.Equal(t, req, record.req) + require.Equal(t, utxoIndex, record.outpointToTxIndex) } // mockers wraps a list of mocked interfaces used inside tx publisher. @@ -369,10 +392,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 +475,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) @@ -650,9 +674,17 @@ func TestTxPublisherBroadcast(t *testing.T) { feerate := chainfee.SatPerKWeight(1000) m.feeFunc.On("FeeRate").Return(feerate) + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + // Create a testing record and put it in the map. fee := btcutil.Amount(1000) - requestID := tp.storeRecord(tx, req, m.feeFunc, fee) + requestID := tp.storeRecord(tx, req, m.feeFunc, fee, utxoIndex) // Quickly check when the requestID cannot be found, an error is // returned. @@ -739,6 +771,14 @@ func TestRemoveResult(t *testing.T) { // Create a testing record and put it in the map. fee := btcutil.Amount(1000) + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + testCases := []struct { name string setupRecord func() uint64 @@ -750,7 +790,9 @@ func TestRemoveResult(t *testing.T) { // removed. name: "remove on TxConfirmed", setupRecord: func() uint64 { - id := tp.storeRecord(tx, req, m.feeFunc, fee) + id := tp.storeRecord( + tx, req, m.feeFunc, fee, utxoIndex, + ) tp.subscriberChans.Store(id, nil) return id @@ -765,7 +807,9 @@ func TestRemoveResult(t *testing.T) { // When the tx is failed, the records will be removed. name: "remove on TxFailed", setupRecord: func() uint64 { - id := tp.storeRecord(tx, req, m.feeFunc, fee) + id := tp.storeRecord( + tx, req, m.feeFunc, fee, utxoIndex, + ) tp.subscriberChans.Store(id, nil) return id @@ -781,7 +825,9 @@ func TestRemoveResult(t *testing.T) { // Noop when the tx is neither confirmed or failed. name: "noop when tx is not confirmed or failed", setupRecord: func() uint64 { - id := tp.storeRecord(tx, req, m.feeFunc, fee) + id := tp.storeRecord( + tx, req, m.feeFunc, fee, utxoIndex, + ) tp.subscriberChans.Store(id, nil) return id @@ -829,9 +875,17 @@ func TestNotifyResult(t *testing.T) { // Create a test tx. tx := &wire.MsgTx{LockTime: 1} + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + // Create a testing record and put it in the map. fee := btcutil.Amount(1000) - requestID := tp.storeRecord(tx, req, m.feeFunc, fee) + requestID := tp.storeRecord(tx, req, m.feeFunc, fee, utxoIndex) // Create a subscription to the event. subscriber := make(chan *BumpResult, 1) @@ -1186,9 +1240,17 @@ func TestHandleTxConfirmed(t *testing.T) { // Create a test tx. tx := &wire.MsgTx{LockTime: 1} + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + // Create a testing record and put it in the map. fee := btcutil.Amount(1000) - requestID := tp.storeRecord(tx, req, m.feeFunc, fee) + requestID := tp.storeRecord(tx, req, m.feeFunc, fee, utxoIndex) record, ok := tp.records.Load(requestID) require.True(t, ok) @@ -1258,9 +1320,17 @@ func TestHandleFeeBumpTx(t *testing.T) { tx: tx, } + op := wire.OutPoint{ + Hash: chainhash.Hash{1}, + Index: 0, + } + utxoIndex := map[wire.OutPoint]int{ + op: 0, + } + // Create a testing record and put it in the map. fee := btcutil.Amount(1000) - requestID := tp.storeRecord(tx, req, m.feeFunc, fee) + requestID := tp.storeRecord(tx, req, m.feeFunc, fee, utxoIndex) // Create a subscription to the event. subscriber := make(chan *BumpResult, 1) diff --git a/sweep/interface.go b/sweep/interface.go index 4b02f143c3..f2fff84b08 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,38 @@ 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, + outpointToTxIndex map[wire.OutPoint]int) error +} diff --git a/sweep/mock_test.go b/sweep/mock_test.go index 605b8d14ec..34202b1453 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, _ map[wire.OutPoint]int) error { + + return nil +} diff --git a/sweep/sweeper.go b/sweep/sweeper.go index af2f0cec9e..a4b6d5638f 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 @@ -796,12 +796,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 @@ -811,7 +818,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. @@ -1702,10 +1709,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 c8d9fc510b..6d9c6c3d2e 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 31f20b7db1..ce144a8eb3 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. @@ -339,7 +373,9 @@ func (b *BudgetInputSet) Budget() btcutil.Amount { budget += input.params.Budget } - return budget + // We'll also tack on the extra budget which will eventually be + // accounted for by the wallet txns when we're broadcasting. + return budget + b.extraBudget } // DeadlineHeight returns the deadline height of the set. diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index b6a87b378b..8d0850b20d 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 30e11023e1..993ee9e59d 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() - - case txscript.IsPayToWitnessScriptHash(outputPkScript): - weightEstimate.addP2WSHOutput() - - case txscript.IsPayToWitnessPubKeyHash(outputPkScript): - weightEstimate.addP2WKHOutput() - - case txscript.IsPayToPubKeyHash(outputPkScript): - weightEstimate.estimator.AddP2PKHOutput() - - case txscript.IsPayToScriptHash(outputPkScript): - weightEstimate.estimator.AddP2SHOutput() - - default: - // Unknown script type. - return nil, nil, errors.New("unknown script type") + // 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.IsPayToWitnessPubKeyHash(outputPkScript): + weightEstimate.addP2WKHOutput() + + case txscript.IsPayToPubKeyHash(outputPkScript): + weightEstimate.estimator.AddP2PKHOutput() + + case txscript.IsPayToScriptHash(outputPkScript): + weightEstimate.estimator.AddP2SHOutput() + + 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 48dcacd499..71477bd6ec 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 { diff --git a/tools/Dockerfile b/tools/Dockerfile index 2545b9c02d..47a081b683 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 +FROM golang:1.22.6 RUN apt-get update && apt-get install -y git ENV GOCACHE=/tmp/build/.cache diff --git a/tools/go.mod b/tools/go.mod index 247f5ca122..02aaaea8d5 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,10 +1,10 @@ module github.com/lightningnetwork/lnd/tools -go 1.19 +go 1.22.6 require ( github.com/btcsuite/btcd v0.23.3 - github.com/golangci/golangci-lint v1.52.2 + github.com/golangci/golangci-lint v1.60.1 github.com/ory/go-acc v0.2.8 github.com/rinchsan/gosimports v0.1.5 ) @@ -12,25 +12,30 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/Abirdcfly/dupword v0.0.11 // indirect - github.com/Antonboom/errname v0.1.9 // indirect - github.com/Antonboom/nilnil v0.1.3 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/4meepo/tagalign v1.3.4 // indirect + github.com/Abirdcfly/dupword v0.0.14 // indirect + github.com/Antonboom/errname v0.1.13 // indirect + github.com/Antonboom/nilnil v0.1.9 // indirect + github.com/Antonboom/testifylint v1.4.3 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.4.0 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/OpenPeeDeeP/depguard v1.1.1 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/aead/siphash v1.0.1 // indirect + github.com/alecthomas/go-check-sumtype v0.1.4 // indirect + github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/ashanbrown/forbidigo v1.5.1 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bkielbasa/cyclop v1.2.0 // indirect + github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v3 v3.4.0 // indirect - github.com/breml/bidichk v0.2.4 // indirect - github.com/breml/errchkjson v0.3.1 // indirect + github.com/bombsimon/wsl/v4 v4.4.1 // indirect + github.com/breml/bidichk v0.2.7 // indirect + github.com/breml/errchkjson v0.3.6 // indirect github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect github.com/btcsuite/btcd/btcutil v1.1.0 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect @@ -38,168 +43,172 @@ require ( github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect - github.com/butuzov/ireturn v0.1.1 // indirect + github.com/butuzov/ireturn v0.3.0 // indirect + github.com/butuzov/mirror v1.2.0 // indirect + github.com/catenacyber/perfsprint v0.7.1 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect - github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.1.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.10.1 // indirect + github.com/daixiang0/gci v0.13.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect - github.com/denis-tingaikin/go-header v0.4.3 // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dgraph-io/ristretto v0.0.2 // indirect - github.com/esimonov/ifshort v1.0.4 // indirect - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/go-critic/go-critic v0.7.0 // indirect + github.com/ghostiam/protogetter v0.3.6 // indirect + github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect - github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect - github.com/golangci/misspell v0.4.0 // indirect - github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 // indirect + github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/modinfo v0.3.4 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/revgrep v0.5.3 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect - github.com/jgautheron/goconst v1.5.1 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect + github.com/jjti/go-spancheck v0.6.2 // indirect github.com/jrick/logrotate v1.0.0 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/junk1tm/musttag v0.5.0 // indirect - github.com/kisielk/errcheck v1.6.3 // indirect - github.com/kisielk/gotool v1.0.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.4 // indirect + github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/kisielk/errcheck v1.7.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.6 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/ldez/gomoddirectives v0.2.3 // indirect - github.com/ldez/tagliatelle v0.4.0 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect + github.com/lasiar/canonicalheader v1.1.1 // indirect + github.com/ldez/gomoddirectives v0.2.4 // indirect + github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect github.com/lufeee/execinquery v1.2.1 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.3.1 // indirect + github.com/mgechev/revive v1.3.9 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moricho/tparallel v0.3.1 // indirect + github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect - github.com/nishanths/exhaustive v0.9.5 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.9.0 // indirect + github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/ory/viper v1.7.5 // indirect github.com/pborman/uuid v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.0 // indirect + github.com/polyfloyd/go-errorlint v1.6.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.3.19 // indirect + github.com/quasilyte/go-ruleguard v0.4.2 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.0 // indirect - github.com/ryanrolds/sqlclosecheck v0.4.0 // indirect + github.com/ryancurrah/gomodguard v1.3.3 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.23.0 // indirect - github.com/securego/gosec/v2 v2.15.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect + github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/sivchari/containedctx v1.0.2 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect - github.com/sivchari/tenv v1.7.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/tenv v1.10.0 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.6.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.11 // indirect - github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e // indirect + github.com/tetafro/godot v1.4.16 // indirect + github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.0.3 // indirect - github.com/ultraware/whitespace v0.0.5 // indirect - github.com/uudashr/gocognit v1.0.6 // indirect + github.com/ultraware/funlen v0.1.0 // indirect + github.com/ultraware/whitespace v0.1.1 // indirect + github.com/uudashr/gocognit v1.1.3 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect - gitlab.com/bosi/decorder v0.2.3 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.12.2 // indirect + go-simpler.org/sloglint v0.7.2 // indirect go.uber.org/atomic v1.7.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.3 // indirect - mvdan.cc/gofumpt v0.4.0 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect + honnef.co/go/tools v0.5.0 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index ab681737bd..a71a0533fc 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -7,7 +7,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -18,9 +17,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -38,59 +34,73 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Abirdcfly/dupword v0.0.11 h1:z6v8rMETchZXUIuHxYNmlUAuKuB21PeaSymTed16wgU= -github.com/Abirdcfly/dupword v0.0.11/go.mod h1:wH8mVGuf3CP5fsBTkfWwwwKTjDnVVCxtU8d8rgeVYXA= -github.com/Antonboom/errname v0.1.9 h1:BZDX4r3l4TBZxZ2o2LNrlGxSHran4d1u4veZdoORTT4= -github.com/Antonboom/errname v0.1.9/go.mod h1:nLTcJzevREuAsgTbG85UsuiWpMpAqbKD1HNZ29OzE58= -github.com/Antonboom/nilnil v0.1.3 h1:6RTbx3d2mcEu3Zwq9TowQpQMVpP75zugwOtqY1RTtcE= -github.com/Antonboom/nilnil v0.1.3/go.mod h1:iOov/7gRcXkeEU+EMGpBu2ORih3iyVEiWjeste1SJm8= +github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= +github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= +github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= +github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= +github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= +github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= +github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= +github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= +github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= +github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= +github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= -github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= +github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= +github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ashanbrown/forbidigo v1.5.1 h1:WXhzLjOlnuDYPYQo/eFlcFMi8X/kLfvWLYu6CSoebis= -github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= -github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= +github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= +github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= -github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= -github.com/breml/bidichk v0.2.4 h1:i3yedFWWQ7YzjdZJHnPo9d/xURinSq3OM+gyM43K4/8= -github.com/breml/bidichk v0.2.4/go.mod h1:7Zk0kRFt1LIZxtQdl9W9JwGAcLTTkOs+tN7wuEYGJ3s= -github.com/breml/errchkjson v0.3.1 h1:hlIeXuspTyt8Y/UmP5qy1JocGNR00KQHgfaNtRAjoxQ= -github.com/breml/errchkjson v0.3.1/go.mod h1:XroxrzKjdiutFyW3nWhw34VGg7kiMsDQox73yWCGI2U= +github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= +github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= +github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= +github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= +github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24= @@ -116,8 +126,14 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= -github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= +github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= +github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= +github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= +github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= +github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -126,26 +142,26 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= +github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.10.1 h1:eheNA3ljF6SxnPD/vE4lCBusVHmV3Rs3dkKvFrJ7MR0= -github.com/daixiang0/gci v0.10.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= +github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -156,8 +172,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -168,20 +184,17 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -189,8 +202,10 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-critic/go-critic v0.7.0 h1:tqbKzB8pqi0NsRZ+1pyU4aweAF7A7QN0Pi4Q02+rYnQ= -github.com/go-critic/go-critic v0.7.0/go.mod h1:moYzd7GdVXE2C2hYTwd7h0CPcqlUeclsyBRwMa38v64= +github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= +github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= +github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -200,31 +215,38 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -254,30 +276,27 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/golangci-lint v1.52.2 h1:FrPElUUI5rrHXg1mQ7KxI1MXPAw5lBVskiz7U7a8a1A= -github.com/golangci/golangci-lint v1.52.2/go.mod h1:S5fhC5sHM5kE22/HcATKd1XLWQxX+y7mHj8B5H91Q/0= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.4.0 h1:KtVB/hTK4bbL/S6bs64rYyk8adjmh1BygbBiaAiX+a0= -github.com/golangci/misspell v0.4.0/go.mod h1:W6O/bwV6lGDxUCChm2ykw9NQdd5bYd1Xkjo88UcWyJc= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= +github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE= +github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= +github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= +github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -292,12 +311,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -305,21 +323,18 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28 h1:9alfqbrhuD+9fLZ4iaAVwhlp5PEhmnBt7yvK2Oy5C1U= -github.com/gordonklaus/ineffassign v0.0.0-20230107090616-13ace0543b28/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= @@ -332,16 +347,13 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -350,19 +362,20 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= +github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= @@ -378,41 +391,45 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/junk1tm/musttag v0.5.0 h1:bV1DTdi38Hi4pG4OVWa7Kap0hi0o7EczuK6wQt9zPOM= -github.com/junk1tm/musttag v0.5.0/go.mod h1:PcR7BA+oREQYvHwgjIDmw3exJeds5JzRcvEJTfjrA0M= +github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= +github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= +github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= +github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= +github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.6 h1:FCKYMF1OF2+RveWlABsdnmsvJrei5aoyZoaGS+Ugg8g= -github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/tagliatelle v0.4.0 h1:sylp7d9kh6AdXN2DpVGHBRb5guTVAgOxqNGhbqc4b1c= -github.com/ldez/tagliatelle v0.4.0/go.mod h1:mNtTfrHy2haaBAw+VT7IBV6VXBThS7TCreYWbBcJ87I= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= +github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= +github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= +github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= +github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= +github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -428,16 +445,14 @@ github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwM github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.3.1 h1:OlQkcH40IB2cGuprTPcjB0iIUddgVZgGmDX3IAMR8D4= -github.com/mgechev/revive v1.3.1/go.mod h1:YlD6TTWl2B8A103R9KWJSPVI9DrEf+oqr15q21Ld+5I= +github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= +github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -449,21 +464,18 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/nishanths/exhaustive v0.9.5 h1:TzssWan6orBiLYVqewCG8faud9qlFntJE30ACpzmGME= -github.com/nishanths/exhaustive v0.9.5/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.9.0 h1:Sm0zX5QfjJzkeCjEp+t6d3Ha0jwvoDjleP9XCsrEzOA= -github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuqe5j4rW1gxJRmI= +github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= +github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -474,18 +486,21 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= +github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= +github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/ory/go-acc v0.2.8 h1:rOHHAPQjf0u7eHFGWpiXK+gIu/e0GRSJNr9pDukdNC4= github.com/ory/go-acc v0.2.8/go.mod h1:iCRZUdGb/7nqvSn8xWZkhfVrtXRZ9Wru2E5rabCjFPI= github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= -github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= @@ -496,17 +511,18 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.0 h1:b+sQ5HibPIAjEZwtuwU8Wz/u0dMZ7YL+bk+9yWyHVJk= -github.com/polyfloyd/go-errorlint v1.4.0/go.mod h1:qJCkPeBn+0EXkdKTrUCcuFStM2xrDKfxI3MGLXPexUs= +github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= +github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -534,8 +550,10 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasilyte/go-ruleguard v0.3.19 h1:tfMnabXle/HzOb5Xe9CUZYWXKfkS1KwRmZyPmD9nVcc= -github.com/quasilyte/go-ruleguard v0.3.19/go.mod h1:lHSn69Scl48I7Gt9cX3VrbsZYvYiBYszZOZW4A+oTEw= +github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= +github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -546,21 +564,24 @@ github.com/rinchsan/gosimports v0.1.5 h1:Z/l9lS79z0xgKC6fLJYmDdY44D0LFwo3MzaMtWv github.com/rinchsan/gosimports v0.1.5/go.mod h1:102/jU2cwf9fpa/YM9D9o4gSen2Vg8Jl80Sxctgd9N0= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= -github.com/ryanrolds/sqlclosecheck v0.4.0 h1:i8SX60Rppc1wRuyQjMciLqIzV3xnoHB7/tXbr6RGYNI= -github.com/ryanrolds/sqlclosecheck v0.4.0/go.mod h1:TBRRjzL31JONc9i4XMinicuo+s+E8yKZ5FN8X3G6CKQ= +github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= +github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1jbEOPrEhDzGYq0= -github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= -github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw= -github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8= +github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= +github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -569,14 +590,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.2 h1:0hLQKpgC53OVF1VT7CeoFHk9YKstur1XOgfYIc1yrHI= -github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= +github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -588,15 +607,15 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -613,9 +632,9 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8L github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -623,45 +642,48 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= -github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e h1:MV6KaVu/hzByHP0UvJ4HcMGE/8a6A4Rggc/0wx2AvJo= -github.com/timakin/bodyclose v0.0.0-20221125081123-e39cf3fc478e/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= +github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= +github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= -github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= +github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= +github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= +github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= +github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= +github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -669,19 +691,27 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= -gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= +go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= +go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= +go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -696,12 +726,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -712,12 +740,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -730,7 +758,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -739,7 +766,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -748,8 +774,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -785,9 +811,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -795,19 +818,15 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -821,8 +840,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -862,17 +882,12 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -883,22 +898,20 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -906,19 +919,17 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -967,14 +978,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -983,15 +987,13 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1012,16 +1014,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1051,13 +1049,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1071,10 +1062,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1087,13 +1074,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1122,16 +1110,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= -honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= -mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= -mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= +honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE= +honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index 8b6c20194f..7780239f07 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -307,9 +308,11 @@ func newTaprootJusticeKit(sweepScript []byte, keyRing := breachInfo.KeyRing + // TODO(roasbeef): aux leaf tower updates needed + tree, err := input.NewLocalCommitScriptTree( breachInfo.RemoteDelay, keyRing.ToLocalKey, - keyRing.RevocationKey, + keyRing.RevocationKey, fn.None[txscript.TapLeaf](), ) if err != nil { return nil, err @@ -416,7 +419,9 @@ func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript, return nil, nil, 0, err } - scriptTree, err := input.NewRemoteCommitScriptTree(toRemotePk) + scriptTree, err := input.NewRemoteCommitScriptTree( + toRemotePk, fn.None[txscript.TapLeaf](), + ) if err != nil { return nil, nil, 0, err } diff --git a/watchtower/blob/justice_kit_test.go b/watchtower/blob/justice_kit_test.go index fd12993a0a..a1d6ec9f2c 100644 --- a/watchtower/blob/justice_kit_test.go +++ b/watchtower/blob/justice_kit_test.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -304,7 +305,9 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) { name: "taproot commitment", blobType: TypeAltruistTaprootCommit, expWitnessScript: func(pk *btcec.PublicKey) []byte { - tree, _ := input.NewRemoteCommitScriptTree(pk) + tree, _ := input.NewRemoteCommitScriptTree( + pk, fn.None[txscript.TapLeaf](), + ) return tree.SettleLeaf.Script }, @@ -461,6 +464,7 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) { script, _ := input.NewLocalCommitScriptTree( csvDelay, delay, rev, + fn.None[txscript.TapLeaf](), ) return script.RevocationLeaf.Script diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index a07b440ad5..5045b4a0f4 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -123,7 +124,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { if isTaprootChannel { toLocalCommitTree, err = input.NewLocalCommitScriptTree( - csvDelay, toLocalPK, revPK, + csvDelay, toLocalPK, revPK, fn.None[txscript.TapLeaf](), ) require.NoError(t, err) @@ -174,7 +175,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toRemoteSequence = 1 commitScriptTree, err := input.NewRemoteCommitScriptTree( - toRemotePK, + toRemotePK, fn.None[txscript.TapLeaf](), ) require.NoError(t, err) diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index 695c4f9ecd..7894631b8f 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -10,6 +10,7 @@ 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/keychain" "github.com/lightningnetwork/lnd/lnwallet" @@ -136,6 +137,7 @@ func genTaskTest( if chanType.IsTaproot() { scriptTree, _ := input.NewLocalCommitScriptTree( csvDelay, toLocalPK, revPK, + fn.None[txscript.TapLeaf](), ) pkScript, _ := input.PayToTaprootScript( @@ -189,7 +191,7 @@ func genTaskTest( if chanType.IsTaproot() { scriptTree, _ := input.NewRemoteCommitScriptTree( - toRemotePK, + toRemotePK, fn.None[txscript.TapLeaf](), ) pkScript, _ := input.PayToTaprootScript( diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index 38d9acd9f0..f3a4d5bf4e 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -19,6 +19,7 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -230,12 +231,14 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) { // Construct the to-local witness script. toLocalScriptTree, err := input.NewLocalCommitScriptTree( - c.csvDelay, c.toLocalPK, c.revPK, + c.csvDelay, c.toLocalPK, c.revPK, fn.None[txscript.TapLeaf](), ) require.NoError(t, err, "unable to create to-local script") // Construct the to-remote witness script. - toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(c.toRemotePK) + toRemoteScriptTree, err := input.NewRemoteCommitScriptTree( + c.toRemotePK, fn.None[txscript.TapLeaf](), + ) require.NoError(t, err, "unable to create to-remote script") // Compute the to-local witness script hash. diff --git a/watchtower/wtclient/queue.go b/watchtower/wtclient/queue.go index 8581ea888f..ee03d288ad 100644 --- a/watchtower/wtclient/queue.go +++ b/watchtower/wtclient/queue.go @@ -323,7 +323,7 @@ func (q *DiskOverflowQueue[T]) drainInputList() { // What we do with this new item depends on what the mode of the // queue currently is. - for q.pushToActiveQueue(task) { //nolint:revive + for q.pushToActiveQueue(task) { // We retry until the task is handled or the quit // channel is closed. } diff --git a/witness_beacon.go b/witness_beacon.go index 4fd44e2837..2bc3c08509 100644 --- a/witness_beacon.go +++ b/witness_beacon.go @@ -101,10 +101,11 @@ func (p *preimageBeacon) SubscribeUpdates( ChanID: chanID, HtlcID: htlc.HtlcIndex, }, - OutgoingChanID: payload.FwdInfo.NextHop, - OutgoingExpiry: payload.FwdInfo.OutgoingCTLV, - OutgoingAmount: payload.FwdInfo.AmountToForward, - CustomRecords: payload.CustomRecords(), + OutgoingChanID: payload.FwdInfo.NextHop, + OutgoingExpiry: payload.FwdInfo.OutgoingCTLV, + OutgoingAmount: payload.FwdInfo.AmountToForward, + InOnionCustomRecords: payload.CustomRecords(), + InWireCustomRecords: htlc.CustomRecords, } copy(packet.OnionBlob[:], nextHopOnionBlob) diff --git a/zpay32/decode.go b/zpay32/decode.go index 05220c862b..61099cf2f0 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" ) @@ -201,7 +202,7 @@ func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) ( errStr += fmt.Sprintf(" %d,", bit) } - return nil, fmt.Errorf(strings.TrimRight(errStr, ",")) + return nil, errors.New(strings.TrimRight(errStr, ",")) } } @@ -278,13 +279,19 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er invoice.PaymentHash, err = parse32Bytes(base32Data) case fieldTypeS: - if invoice.PaymentAddr != nil { + if invoice.PaymentAddr.IsSome() { // We skip the field if we have already seen a // supported one. continue } - invoice.PaymentAddr, err = parse32Bytes(base32Data) + addr, err := parse32Bytes(base32Data) + if err != nil { + return err + } + if addr != nil { + invoice.PaymentAddr = fn.Some(*addr) + } case fieldTypeD: if invoice.Description != nil { diff --git a/zpay32/encode.go b/zpay32/encode.go index 130abdcfd5..3e2d799776 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" ) @@ -301,14 +302,14 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { return err } } - if invoice.PaymentAddr != nil { - err := writeBytes32( - bufferBase32, fieldTypeS, *invoice.PaymentAddr, - ) - if err != nil { - return err - } + + err := fn.MapOptionZ(invoice.PaymentAddr, func(addr [32]byte) error { + return writeBytes32(bufferBase32, fieldTypeS, addr) + }) + if err != nil { + return err } + if invoice.Features.SerializeSize32() > 0 { var b bytes.Buffer err := invoice.Features.RawFeatureVector.EncodeBase32(&b) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 8b1457ab43..7c18253eb0 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" ) @@ -136,7 +137,7 @@ type Invoice struct { // PaymentAddr is the payment address to be used by payments to prevent // probing of the destination. - PaymentAddr *[32]byte + PaymentAddr fn.Option[[32]byte] // Destination is the public key of the target node. This will always // be set after decoding, and can optionally be set before encoding to @@ -301,7 +302,7 @@ func Features(features *lnwire.FeatureVector) func(*Invoice) { // the desired payment address that is advertised on the invoice. func PaymentAddr(addr [32]byte) func(*Invoice) { return func(i *Invoice) { - i.PaymentAddr = &addr + i.PaymentAddr = fn.Some(addr) } } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 6a726daf37..de59422aba 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -104,12 +105,8 @@ var ( testMessageSigner = MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - sig, err := ecdsa.SignCompact(testPrivKey, hash, true) - if err != nil { - return nil, fmt.Errorf("can't sign the "+ - "message: %v", err) - } - return sig, nil + + return ecdsa.SignCompact(testPrivKey, hash, true), nil }, } @@ -562,7 +559,7 @@ func TestDecodeEncode(t *testing.T) { MilliSat: &testMillisat25mBTC, Timestamp: time.Unix(1496314658, 0), PaymentHash: &testPaymentHash, - PaymentAddr: &specPaymentAddr, + PaymentAddr: fn.Some(specPaymentAddr), Description: &testCoffeeBeans, Destination: testPubKey, Features: lnwire.NewFeatureVector( @@ -589,7 +586,7 @@ func TestDecodeEncode(t *testing.T) { MilliSat: &testMillisat25mBTC, Timestamp: time.Unix(1496314658, 0), PaymentHash: &testPaymentHash, - PaymentAddr: &specPaymentAddr, + PaymentAddr: fn.Some(specPaymentAddr), Description: &testCoffeeBeans, Destination: testPubKey, Features: lnwire.NewFeatureVector( @@ -718,7 +715,7 @@ func TestDecodeEncode(t *testing.T) { PaymentHash: &testPaymentHash, Description: &testPaymentMetadata, Destination: testPubKey, - PaymentAddr: &specPaymentAddr, + PaymentAddr: fn.Some(specPaymentAddr), Features: lnwire.NewFeatureVector( lnwire.NewRawFeatureVector(8, 14, 48), lnwire.Features, @@ -772,7 +769,7 @@ func TestDecodeEncode(t *testing.T) { MilliSat: &testMillisat25mBTC, Timestamp: time.Unix(1496314658, 0), PaymentHash: &testPaymentHash, - PaymentAddr: &specPaymentAddr, + PaymentAddr: fn.Some(specPaymentAddr), Description: &testCoffeeBeans, Destination: testPubKey, Features: lnwire.NewFeatureVector( @@ -803,7 +800,7 @@ func TestDecodeEncode(t *testing.T) { MilliSat: &testMillisat25mBTC, Timestamp: time.Unix(1496314658, 0), PaymentHash: &testPaymentHash, - PaymentAddr: &specPaymentAddr, + PaymentAddr: fn.Some(specPaymentAddr), Description: &testCoffeeBeans, Destination: testPubKey, Features: lnwire.NewFeatureVector( @@ -1042,7 +1039,7 @@ func TestInvoiceChecksumMalleability(t *testing.T) { msgSigner := MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := chainhash.HashB(msg) - return ecdsa.SignCompact(privKey, hash, true) + return ecdsa.SignCompact(privKey, hash, true), nil }, } opts := []func(*Invoice){Description("test")}