Merge pull request #7575 from lightningnetwork/0-16-1-staging

lnd: merge the 0.16.1 staging branch into master
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2023-04-05 16:48:25 -07:00
commit 2596a33529
90 files changed
+7862 -3500

No files matched your search

+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
name: Bitcoind Integration ARM name: Bitcoind Integration ARM
script: script:
- bash ./scripts/install_bitcoind.sh - bash ./scripts/install_bitcoind.sh
- GOMEMLIMIT=500MiB GOARM=7 GOARCH=arm GOOS=linux travis_wait 30 make itest-parallel backend=bitcoind - GOMEMLIMIT=1024MiB GOARM=7 GOARCH=arm GOOS=linux travis_wait 120 make itest-parallel backend=bitcoind tranches=8
arch: arm64 arch: arm64
after_failure: after_failure:
+10 -10
View File
@@ -10,7 +10,7 @@ GOIMPORTS_PKG := github.com/rinchsan/gosimports/cmd/gosimports
GO_BIN := ${GOPATH}/bin GO_BIN := ${GOPATH}/bin
BTCD_BIN := $(GO_BIN)/btcd BTCD_BIN := $(GO_BIN)/btcd
GOIMPORTS_BIN := $(GO_BIN)/gosimports GOIMPORTS_BIN := $(GO_BIN)/gosimports
GOMOBILE_BIN := GO111MODULE=off $(GO_BIN)/gomobile GOMOBILE_BIN := $(GO_BIN)/gomobile
GOACC_BIN := $(GO_BIN)/go-acc GOACC_BIN := $(GO_BIN)/go-acc
MOBILE_BUILD_DIR :=${GOPATH}/src/$(MOBILE_PKG)/build MOBILE_BUILD_DIR :=${GOPATH}/src/$(MOBILE_PKG)/build
@@ -190,7 +190,7 @@ unit-debug: $(BTCD_BIN)
unit-cover: $(GOACC_BIN) unit-cover: $(GOACC_BIN)
@$(call print, "Running unit coverage tests.") @$(call print, "Running unit coverage tests.")
$(GOACC_BIN) $(COVER_PKG) -- -tags="$(DEV_TAGS) $(LOG_TAGS)" $(GOACC)
unit-race: unit-race:
@$(call print, "Running unit race tests.") @$(call print, "Running unit race tests.")
@@ -274,25 +274,25 @@ vendor:
@$(call print, "Re-creating vendor directory.") @$(call print, "Re-creating vendor directory.")
rm -r vendor/; go mod vendor rm -r vendor/; go mod vendor
apple: vendor mobile-rpc apple: mobile-rpc
@$(call print, "Building iOS and macOS cxframework ($(IOS_BUILD)).") @$(call print, "Building iOS and macOS cxframework ($(IOS_BUILD)).")
mkdir -p $(IOS_BUILD_DIR) mkdir -p $(IOS_BUILD_DIR)
$(GOMOBILE_BIN) bind -target=ios,iossimulator,macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(RELEASE_LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) $(GOMOBILE_BIN) bind -target=ios,iossimulator,macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
ios: vendor mobile-rpc ios: mobile-rpc
@$(call print, "Building iOS cxframework ($(IOS_BUILD)).") @$(call print, "Building iOS cxframework ($(IOS_BUILD)).")
mkdir -p $(IOS_BUILD_DIR) mkdir -p $(IOS_BUILD_DIR)
$(GOMOBILE_BIN) bind -target=ios,iossimulator -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(RELEASE_LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) $(GOMOBILE_BIN) bind -target=ios,iossimulator -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
macos: vendor mobile-rpc macos: mobile-rpc
@$(call print, "Building macOS cxframework ($(IOS_BUILD)).") @$(call print, "Building macOS cxframework ($(IOS_BUILD)).")
mkdir -p $(IOS_BUILD_DIR) mkdir -p $(IOS_BUILD_DIR)
$(GOMOBILE_BIN) bind -target=macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(RELEASE_LDFLAGS) -v -o $(IOS_BUILD) $(MOBILE_PKG) $(GOMOBILE_BIN) bind -target=macos -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG)
android: vendor mobile-rpc android: mobile-rpc
@$(call print, "Building Android library ($(ANDROID_BUILD)).") @$(call print, "Building Android library ($(ANDROID_BUILD)).")
mkdir -p $(ANDROID_BUILD_DIR) mkdir -p $(ANDROID_BUILD_DIR)
$(GOMOBILE_BIN) bind -target=android -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" $(RELEASE_LDFLAGS) -v -o $(ANDROID_BUILD) $(MOBILE_PKG) $(GOMOBILE_BIN) bind -target=android -androidapi 21 -tags="mobile $(DEV_TAGS) $(RPC_TAGS)" -ldflags "$(RELEASE_LDFLAGS)" -v -o $(ANDROID_BUILD) $(MOBILE_PKG)
mobile: ios android mobile: ios android
+3 -3
View File
@@ -38,7 +38,7 @@ var (
lastAliasKey = []byte("last-alias-key") lastAliasKey = []byte("last-alias-key")
// invoiceAliasBucket is a root-level bucket that stores the alias // invoiceAliasBucket is a root-level bucket that stores the alias
// SCIDs that our peers send us in the funding_locked TLV. The keys are // SCIDs that our peers send us in the channel_ready TLV. The keys are
// the ChannelID generated from the FundingOutpoint and the values are // the ChannelID generated from the FundingOutpoint and the values are
// the remote peer's alias SCID. // the remote peer's alias SCID.
invoiceAliasBucket = []byte("invoice-alias-bucket") invoiceAliasBucket = []byte("invoice-alias-bucket")
@@ -88,7 +88,7 @@ type Manager struct {
aliasToBase map[lnwire.ShortChannelID]lnwire.ShortChannelID aliasToBase map[lnwire.ShortChannelID]lnwire.ShortChannelID
// peerAlias is a cache for the alias SCIDs that our peers send us in // peerAlias is a cache for the alias SCIDs that our peers send us in
// the funding_locked TLV. The keys are the ChannelID generated from // the channel_ready TLV. The keys are the ChannelID generated from
// the FundingOutpoint and the values are the remote peer's alias SCID. // the FundingOutpoint and the values are the remote peer's alias SCID.
// The values should match the ones stored in the "invoice-alias-bucket" // The values should match the ones stored in the "invoice-alias-bucket"
// bucket. // bucket.
@@ -341,7 +341,7 @@ func (m *Manager) DeleteSixConfs(baseScid lnwire.ShortChannelID) error {
} }
// PutPeerAlias stores the peer's alias SCID once we learn of it in the // PutPeerAlias stores the peer's alias SCID once we learn of it in the
// funding_locked message. // channel_ready message.
func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID, func (m *Manager) PutPeerAlias(chanID lnwire.ChannelID,
alias lnwire.ShortChannelID) error { alias lnwire.ShortChannelID) error {
+2 -2
View File
@@ -1345,7 +1345,7 @@ func (c *OpenChannel) MarkBorked() error {
} }
// SecondCommitmentPoint returns the second per-commitment-point for use in the // SecondCommitmentPoint returns the second per-commitment-point for use in the
// funding_locked message. // channel_ready message.
func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
@@ -3770,7 +3770,7 @@ func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) erro
// TODO(roasbeef): don't keep producer on disk // TODO(roasbeef): don't keep producer on disk
// If the next revocation is present, which is only the case after the // If the next revocation is present, which is only the case after the
// FundingLocked message has been sent, then we'll write it to disk. // ChannelReady message has been sent, then we'll write it to disk.
if channel.RemoteNextRevocation != nil { if channel.RemoteNextRevocation != nil {
err = WriteElements(&b, channel.RemoteNextRevocation) err = WriteElements(&b, channel.RemoteNextRevocation)
if err != nil { if err != nil {
+88 -14
View File
@@ -21,6 +21,7 @@ import (
"github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightninglabs/protobuf-hex-display/jsonpb"
"github.com/lightninglabs/protobuf-hex-display/proto" "github.com/lightninglabs/protobuf-hex-display/proto"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/signal"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -2029,7 +2030,7 @@ var updateChannelPolicyCommand = cli.Command{
"with a granularity of 0.000001 (millionths). Can not " + "with a granularity of 0.000001 (millionths). Can not " +
"be set at the same time as fee_rate.", "be set at the same time as fee_rate.",
}, },
cli.Int64Flag{ cli.Uint64Flag{
Name: "time_lock_delta", Name: "time_lock_delta",
Usage: "the CLTV delta that will be applied to all " + Usage: "the CLTV delta that will be applied to all " +
"forwarded HTLCs", "forwarded HTLCs",
@@ -2080,6 +2081,25 @@ func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) {
}, nil }, nil
} }
// parseTimeLockDelta is expected to get a uint16 type of timeLockDelta,
// which maximum value is MaxTimeLockDelta.
func parseTimeLockDelta(timeLockDeltaStr string) (uint16, error) {
timeLockDeltaUnCheck, err := strconv.ParseUint(
timeLockDeltaStr, 10, 64,
)
if err != nil {
return 0, fmt.Errorf("failed to parse time_lock_delta: %s "+
"to uint64, err: %v", timeLockDeltaStr, err)
}
if timeLockDeltaUnCheck > routing.MaxCLTVDelta {
return 0, fmt.Errorf("time_lock_delta is too big, "+
"max value is %d", routing.MaxCLTVDelta)
}
return uint16(timeLockDeltaUnCheck), nil
}
func updateChannelPolicy(ctx *cli.Context) error { func updateChannelPolicy(ctx *cli.Context) error {
ctxc := getContext() ctxc := getContext()
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
@@ -2089,7 +2109,7 @@ func updateChannelPolicy(ctx *cli.Context) error {
baseFee int64 baseFee int64
feeRate float64 feeRate float64
feeRatePpm uint64 feeRatePpm uint64
timeLockDelta int64 timeLockDelta uint16
err error err error
) )
args := ctx.Args() args := ctx.Args()
@@ -2127,12 +2147,15 @@ func updateChannelPolicy(ctx *cli.Context) error {
switch { switch {
case ctx.IsSet("time_lock_delta"): case ctx.IsSet("time_lock_delta"):
timeLockDelta = ctx.Int64("time_lock_delta") timeLockDeltaStr := ctx.String("time_lock_delta")
case args.Present(): timeLockDelta, err = parseTimeLockDelta(timeLockDeltaStr)
timeLockDelta, err = strconv.ParseInt(args.First(), 10, 64)
if err != nil { if err != nil {
return fmt.Errorf("unable to decode time_lock_delta: %v", return err
err) }
case args.Present():
timeLockDelta, err = parseTimeLockDelta(args.First())
if err != nil {
return err
} }
args = args.Tail() args = args.Tail()
@@ -2291,8 +2314,9 @@ func exportChanBackup(ctx *cli.Context) error {
} }
var ( var (
err error err error
chanPointStr string chanPointStr string
outputFileName string
) )
args := ctx.Args() args := ctx.Args()
@@ -2307,6 +2331,10 @@ func exportChanBackup(ctx *cli.Context) error {
return fmt.Errorf("must specify chan_point if --all isn't set") return fmt.Errorf("must specify chan_point if --all isn't set")
} }
if ctx.IsSet("output_file") {
outputFileName = ctx.String("output_file")
}
if chanPointStr != "" { if chanPointStr != "" {
chanPointRPC, err := parseChanPoint(chanPointStr) chanPointRPC, err := parseChanPoint(chanPointStr)
if err != nil { if err != nil {
@@ -2334,6 +2362,14 @@ func exportChanBackup(ctx *cli.Context) error {
Index: chanPointRPC.OutputIndex, Index: chanPointRPC.OutputIndex,
} }
if outputFileName != "" {
return os.WriteFile(
outputFileName,
chanBackup.ChanBackup,
0666,
)
}
printJSON(struct { printJSON(struct {
ChanPoint string `json:"chan_point"` ChanPoint string `json:"chan_point"`
ChanBackup []byte `json:"chan_backup"` ChanBackup []byte `json:"chan_backup"`
@@ -2355,9 +2391,9 @@ func exportChanBackup(ctx *cli.Context) error {
return err return err
} }
if ctx.IsSet("output_file") { if outputFileName != "" {
return ioutil.WriteFile( return os.WriteFile(
ctx.String("output_file"), outputFileName,
chanBackup.MultiChanBackup.MultiChanBackup, chanBackup.MultiChanBackup.MultiChanBackup,
0666, 0666,
) )
@@ -2393,13 +2429,16 @@ var verifyChanBackupCommand = cli.Command{
backup for integrity. This is useful when a user has a backup, but is backup for integrity. This is useful when a user has a backup, but is
unsure as to if it's valid or for the target node. unsure as to if it's valid or for the target node.
The command will accept backups in one of three forms: The command will accept backups in one of four forms:
* A single channel packed SCB, which can be obtained from * A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format. exportchanbackup. This should be passed in hex encoded format.
* A packed multi-channel SCB, which couples several individual * A packed multi-channel SCB, which couples several individual
static channel backups in single blob. static channel backups in single blob.
* A file path which points to a packed single-channel backup within a
file, using the same format that lnd does in its channel.backup file.
* A file path which points to a packed multi-channel backup within a * A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channel.backup file, using the same format that lnd does in its channel.backup
@@ -2416,6 +2455,13 @@ var verifyChanBackupCommand = cli.Command{
Usage: "a hex encoded multi-channel backup obtained " + Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup", "from exportchanbackup",
}, },
cli.StringFlag{
Name: "single_file",
Usage: "the path to a single-channel backup file",
TakesFile: true,
},
cli.StringFlag{ cli.StringFlag{
Name: "multi_file", Name: "multi_file",
Usage: "the path to a multi-channel back up file", Usage: "the path to a multi-channel back up file",
@@ -2476,7 +2522,7 @@ var restoreChanBackupCommand = cli.Command{
channel. If successful, this command will allows the user to recover channel. If successful, this command will allows the user to recover
the settled funds stored in the recovered channels. the settled funds stored in the recovered channels.
The command will accept backups in one of three forms: The command will accept backups in one of four forms:
* A single channel packed SCB, which can be obtained from * A single channel packed SCB, which can be obtained from
exportchanbackup. This should be passed in hex encoded format. exportchanbackup. This should be passed in hex encoded format.
@@ -2484,6 +2530,10 @@ var restoreChanBackupCommand = cli.Command{
* A packed multi-channel SCB, which couples several individual * A packed multi-channel SCB, which couples several individual
static channel backups in single blob. static channel backups in single blob.
* A file path which points to a packed single-channel backup within
a file, using the same format that lnd does in its channel.backup
file.
* A file path which points to a packed multi-channel backup within a * A file path which points to a packed multi-channel backup within a
file, using the same format that lnd does in its channel.backup file, using the same format that lnd does in its channel.backup
file. file.
@@ -2499,6 +2549,13 @@ var restoreChanBackupCommand = cli.Command{
Usage: "a hex encoded multi-channel backup obtained " + Usage: "a hex encoded multi-channel backup obtained " +
"from exportchanbackup", "from exportchanbackup",
}, },
cli.StringFlag{
Name: "single_file",
Usage: "the path to a single-channel backup file",
TakesFile: true,
},
cli.StringFlag{ cli.StringFlag{
Name: "multi_file", Name: "multi_file",
Usage: "the path to a multi-channel back up file", Usage: "the path to a multi-channel back up file",
@@ -2550,6 +2607,23 @@ func parseChanBackups(ctx *cli.Context) (*lnrpc.RestoreChanBackupRequest, error)
}, },
}, nil }, nil
case ctx.IsSet("single_file"):
packedSingle, err := os.ReadFile(ctx.String("single_file"))
if err != nil {
return nil, fmt.Errorf("unable to decode single "+
"packed backup: %v", err)
}
return &lnrpc.RestoreChanBackupRequest{
Backup: &lnrpc.RestoreChanBackupRequest_ChanBackups{
ChanBackups: &lnrpc.ChannelBackups{
ChanBackups: []*lnrpc.ChannelBackup{{
ChanBackup: packedSingle,
}},
},
},
}, nil
case ctx.IsSet("multi_file"): case ctx.IsSet("multi_file"):
packedMulti, err := ioutil.ReadFile(ctx.String("multi_file")) packedMulti, err := ioutil.ReadFile(ctx.String("multi_file"))
if err != nil { if err != nil {
+53
View File
@@ -2,6 +2,9 @@ package main
import ( import (
"errors" "errors"
"fmt"
"math"
"strconv"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -68,3 +71,53 @@ func TestParseChanPoint(t *testing.T) {
} }
} }
} }
// TestParseTimeLockDelta tests parseTimeLockDelta with various
// valid and invalid input values and verifies the output.
func TestParseTimeLockDelta(t *testing.T) {
t.Parallel()
testCases := []struct {
timeLockDeltaStr string
expectedTimeLockDelta uint16
expectErr bool
expectedErrContent string
}{
{
timeLockDeltaStr: "-1",
expectErr: true,
expectedErrContent: fmt.Sprintf(
"failed to parse time_lock_delta: %d "+
"to uint64", -1,
),
},
{
timeLockDeltaStr: "0",
},
{
timeLockDeltaStr: "3",
expectedTimeLockDelta: 3,
},
{
timeLockDeltaStr: strconv.FormatUint(
uint64(math.MaxUint16), 10,
),
expectedTimeLockDelta: math.MaxUint16,
},
{
timeLockDeltaStr: "18446744073709551616",
expectErr: true,
expectedErrContent: fmt.Sprint(
"failed to parse time_lock_delta:" +
" 18446744073709551616 to uint64"),
},
}
for _, tc := range testCases {
timeLockDelta, err := parseTimeLockDelta(tc.timeLockDeltaStr)
require.Equal(t, tc.expectedTimeLockDelta, timeLockDelta)
if tc.expectErr {
require.ErrorContains(t, err, tc.expectedErrContent)
} else {
require.NoError(t, err)
}
}
}
+11 -1
View File
@@ -660,6 +660,13 @@ var fundPsbtCommand = cli.Command{
"always use the coin selection key scope to " + "always use the coin selection key scope to " +
"generate the change address", "generate the change address",
}, },
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of " +
"confirmations each input used for the PSBT " +
"transaction must satisfy",
Value: defaultUtxoMinConf,
},
}, },
Action: actionDecorator(fundPsbt), Action: actionDecorator(fundPsbt),
} }
@@ -673,8 +680,11 @@ func fundPsbt(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, "fund") return cli.ShowCommandHelp(ctx, "fund")
} }
minConfs := int32(ctx.Uint64("min_confs"))
req := &walletrpc.FundPsbtRequest{ req := &walletrpc.FundPsbtRequest{
Account: ctx.String("account"), Account: ctx.String("account"),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
} }
// Parse template flags. // Parse template flags.
+20 -2
View File
@@ -151,6 +151,12 @@ var listTowersCommand = cli.Command{
Usage: "include sessions with the watchtower in the " + Usage: "include sessions with the watchtower in the " +
"response", "response",
}, },
cli.BoolFlag{
Name: "exclude_exhausted_sessions",
Usage: "Whether to exclude exhausted sessions in " +
"the response info. This option is only " +
"meaningful if include_sessions is true",
},
}, },
Action: actionDecorator(listTowers), Action: actionDecorator(listTowers),
} }
@@ -160,7 +166,7 @@ func listTowers(ctx *cli.Context) error {
// Display the command's help message if the number of arguments/flags // Display the command's help message if the number of arguments/flags
// is not what we expect. // is not what we expect.
if ctx.NArg() > 0 || ctx.NumFlags() > 1 { if ctx.NArg() > 0 || ctx.NumFlags() > 2 {
return cli.ShowCommandHelp(ctx, "towers") return cli.ShowCommandHelp(ctx, "towers")
} }
@@ -169,6 +175,9 @@ func listTowers(ctx *cli.Context) error {
req := &wtclientrpc.ListTowersRequest{ req := &wtclientrpc.ListTowersRequest{
IncludeSessions: ctx.Bool("include_sessions"), IncludeSessions: ctx.Bool("include_sessions"),
ExcludeExhaustedSessions: ctx.Bool(
"exclude_exhausted_sessions",
),
} }
resp, err := client.ListTowers(ctxc, req) resp, err := client.ListTowers(ctxc, req)
if err != nil { if err != nil {
@@ -190,6 +199,12 @@ var getTowerCommand = cli.Command{
Usage: "include sessions with the watchtower in the " + Usage: "include sessions with the watchtower in the " +
"response", "response",
}, },
cli.BoolFlag{
Name: "exclude_exhausted_sessions",
Usage: "Whether to exclude exhausted sessions in " +
"the response info. This option is only " +
"meaningful if include_sessions is true",
},
}, },
Action: actionDecorator(getTower), Action: actionDecorator(getTower),
} }
@@ -199,7 +214,7 @@ func getTower(ctx *cli.Context) error {
// Display the command's help message if the number of arguments/flags // Display the command's help message if the number of arguments/flags
// is not what we expect. // is not what we expect.
if ctx.NArg() != 1 || ctx.NumFlags() > 1 { if ctx.NArg() != 1 || ctx.NumFlags() > 2 {
return cli.ShowCommandHelp(ctx, "tower") return cli.ShowCommandHelp(ctx, "tower")
} }
@@ -217,6 +232,9 @@ func getTower(ctx *cli.Context) error {
req := &wtclientrpc.GetTowerInfoRequest{ req := &wtclientrpc.GetTowerInfoRequest{
Pubkey: pubKey, Pubkey: pubKey,
IncludeSessions: ctx.Bool("include_sessions"), IncludeSessions: ctx.Bool("include_sessions"),
ExcludeExhaustedSessions: ctx.Bool(
"exclude_exhausted_sessions",
),
} }
resp, err := client.GetTowerInfo(ctxc, req) resp, err := client.GetTowerInfo(ctxc, req)
if err != nil { if err != nil {
+4
View File
@@ -98,6 +98,10 @@ const (
// HTLCs on our channels. // HTLCs on our channels.
minTimeLockDelta = routing.MinCLTVDelta minTimeLockDelta = routing.MinCLTVDelta
// MaxTimeLockDelta is the maximum CLTV delta that can be applied to
// forwarded HTLCs.
MaxTimeLockDelta = routing.MaxCLTVDelta
// defaultAcceptorTimeout is the time after which an RPCAcceptor will time // defaultAcceptorTimeout is the time after which an RPCAcceptor will time
// out and return false if it hasn't yet received a response. // out and return false if it hasn't yet received a response.
defaultAcceptorTimeout = 15 * time.Second defaultAcceptorTimeout = 15 * time.Second
+1 -1
View File
@@ -1294,7 +1294,7 @@ func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) {
// We'll first attempt to filter out this new message for all peers // We'll first attempt to filter out this new message for all peers
// that have active gossip syncers active. // that have active gossip syncers active.
for pub, syncer := range syncerPeers { for pub, syncer := range syncerPeers {
log.Tracef("Sending messages batch to GossipSyncer(%x)", pub) log.Tracef("Sending messages batch to GossipSyncer(%s)", pub)
syncer.FilterGossipMsgs(annBatch...) syncer.FilterGossipMsgs(annBatch...)
} }
+8 -1
View File
@@ -111,7 +111,11 @@ func messageStoreKey(msg lnwire.Message, peerPubKey [33]byte) ([]byte, error) {
// AddMessage adds a message to the store for this peer. // AddMessage adds a message to the store for this peer.
func (s *MessageStore) AddMessage(msg lnwire.Message, peerPubKey [33]byte) error { func (s *MessageStore) AddMessage(msg lnwire.Message, peerPubKey [33]byte) error {
// Construct the key for which we'll find this message with in the store. log.Tracef("Adding message of type %v to store for peer %x",
msg.MsgType(), peerPubKey)
// Construct the key for which we'll find this message with in the
// store.
msgKey, err := messageStoreKey(msg, peerPubKey) msgKey, err := messageStoreKey(msg, peerPubKey)
if err != nil { if err != nil {
return err return err
@@ -137,6 +141,9 @@ func (s *MessageStore) AddMessage(msg lnwire.Message, peerPubKey [33]byte) error
func (s *MessageStore) DeleteMessage(msg lnwire.Message, func (s *MessageStore) DeleteMessage(msg lnwire.Message,
peerPubKey [33]byte) error { peerPubKey [33]byte) error {
log.Tracef("Deleting message of type %v from store for peer %x",
msg.MsgType(), peerPubKey)
// Construct the key for which we'll find this message with in the // Construct the key for which we'll find this message with in the
// store. // store.
msgKey, err := messageStoreKey(msg, peerPubKey) msgKey, err := messageStoreKey(msg, peerPubKey)
@@ -0,0 +1,72 @@
# Release Notes
## `lncli`
* The `lncli wallet psbt fund` command now allows users to specify the
[`--min_confs` flag](https://github.com/lightningnetwork/lnd/pull/7510).
* [Add time_lock_delta overflow check for UpdateChannelPolicy](https://github.com/lightningnetwork/lnd/pull/7350)
that ensure `time_lock_delta` is greater or equal than `0` and less or equal than `65535`
* [Added ability to backup, verify and
restore single channels](https://github.com/lightningnetwork/lnd/pull/7437)
to and from a file on disk.
## Watchtowers
* [Fix Address iterator
panic](https://github.com/lightningnetwork/lnd/pull/7556)
* [Allow caller to filter sessions at the time of reading them from
disk](https://github.com/lightningnetwork/lnd/pull/7059)
* [Clean up sessions once all channels for which they have updates for are
closed. Also start sending the `DeleteSession` message to the
tower.](https://github.com/lightningnetwork/lnd/pull/7069)
* [Don't load exhausted sessions when not
needed](https://github.com/lightningnetwork/lnd/pull/7405). Also add a new
`exclude_exhausted_sessions` boolean flag to the relevant lncli wtclient
commands.
* [Recover from StateUpdateCodeClientBehind
error](https://github.com/lightningnetwork/lnd/pull/7541) after data loss.
## Build
* [ldflags were being incorrectly passed](
https://github.com/lightningnetwork/lnd/pull/7359)
## Misc
* [Return `FEE_INSUFFICIENT` before checking balance for incoming low-fee
HTLCs.](https://github.com/lightningnetwork/lnd/pull/7490).
## Spec
* [Add test vectors for
option_zero_fee_htlc_tx](https://github.com/lightningnetwork/lnd/pull/7439)
## RPC
* A [debug log](https://github.com/lightningnetwork/lnd/pull/7514) has been
added to `lnrpc` so the node operator can know whether a certain request has
happened or not.
* [Add peer_scid_alias to the response of
`listchannels`](https://github.com/lightningnetwork/lnd/pull/7366)
* Message `funding_locked` [has been
renamed](https://github.com/lightningnetwork/lnd/pull/7517) to
`channel_ready` internally to match the specs update. This should not change
anything for the users since the message type(36) stays unchanged, except in
the logging all the appearance of `funding_locked` replated experssion is
replaced with `channel_ready`.
## Bug Fixes
* [Fix a bug where lnd crashes when psbt data is not fully
available](https://github.com/lightningnetwork/lnd/pull/7529).
# Contributors (Alphabetical Order)
* ardevd
* Elle Mouton
* Oliver Gugger
* Tommy Volk
* Yong Yu
* ziggie1984
+1 -1
View File
@@ -7,5 +7,5 @@ import "time"
func init() { func init() {
// For itest, we will use a much shorter checking interval here as // For itest, we will use a much shorter checking interval here as
// local communications are very fast. // local communications are very fast.
checkPeerFundingLockInterval = 10 * time.Millisecond checkPeerChannelReadyInterval = 10 * time.Millisecond
} }
+3 -3
View File
@@ -23,15 +23,15 @@ type Controller interface {
// aliasHandler is an interface that abstracts the managing of aliases. // aliasHandler is an interface that abstracts the managing of aliases.
type aliasHandler interface { type aliasHandler interface {
// RequestAlias lets the funding manager request a unique SCID alias to // RequestAlias lets the funding manager request a unique SCID alias to
// use in the funding_locked message. // use in the channel_ready message.
RequestAlias() (lnwire.ShortChannelID, error) RequestAlias() (lnwire.ShortChannelID, error)
// PutPeerAlias lets the funding manager store the received alias SCID // PutPeerAlias lets the funding manager store the received alias SCID
// in the funding_locked message. // in the channel_ready message.
PutPeerAlias(lnwire.ChannelID, lnwire.ShortChannelID) error PutPeerAlias(lnwire.ChannelID, lnwire.ShortChannelID) error
// GetPeerAlias lets the funding manager lookup the received alias SCID // GetPeerAlias lets the funding manager lookup the received alias SCID
// from the funding_locked message. This is not the same as GetAliases // from the channel_ready message. This is not the same as GetAliases
// which retrieves OUR aliases for a given channel. // which retrieves OUR aliases for a given channel.
GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, error) GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, error)
+99 -85
View File
@@ -40,12 +40,12 @@ var (
// buffers. // buffers.
byteOrder = binary.BigEndian byteOrder = binary.BigEndian
// checkPeerFundingLockInterval is used when we are waiting for the // checkPeerChannelReadyInterval is used when we are waiting for the
// peer to send us FundingLocked. We will check every 1 second to see // peer to send us ChannelReady. We will check every 1 second to see
// if the message is received. // if the message is received.
// //
// NOTE: for itest, this value is changed to 10ms. // NOTE: for itest, this value is changed to 10ms.
checkPeerFundingLockInterval = 1 * time.Second checkPeerChannelReadyInterval = 1 * time.Second
) )
// WriteOutpoint writes an outpoint to an io.Writer. This is not the same as // WriteOutpoint writes an outpoint to an io.Writer. This is not the same as
@@ -377,7 +377,7 @@ type Config struct {
// NotifyWhenOnline allows the FundingManager to register with a // NotifyWhenOnline allows the FundingManager to register with a
// subsystem that will notify it when the peer comes online. This is // subsystem that will notify it when the peer comes online. This is
// used when sending the fundingLocked message, since it MUST be // used when sending the channelReady message, since it MUST be
// delivered after the funding transaction is confirmed. // delivered after the funding transaction is confirmed.
// //
// NOTE: The peerChan channel must be buffered. // NOTE: The peerChan channel must be buffered.
@@ -570,8 +570,8 @@ type Manager struct {
localDiscoveryMtx sync.Mutex localDiscoveryMtx sync.Mutex
localDiscoverySignals map[lnwire.ChannelID]chan struct{} localDiscoverySignals map[lnwire.ChannelID]chan struct{}
handleFundingLockedMtx sync.RWMutex handleChannelReadyMtx sync.RWMutex
handleFundingLockedBarriers map[lnwire.ChannelID]struct{} handleChannelReadyBarriers map[lnwire.ChannelID]struct{}
quit chan struct{} quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
@@ -584,18 +584,18 @@ type channelOpeningState uint8
const ( const (
// markedOpen is the opening state of a channel if the funding // markedOpen is the opening state of a channel if the funding
// transaction is confirmed on-chain, but fundingLocked is not yet // transaction is confirmed on-chain, but channelReady is not yet
// successfully sent to the other peer. // successfully sent to the other peer.
markedOpen channelOpeningState = iota markedOpen channelOpeningState = iota
// fundingLockedSent is the opening state of a channel if the // channelReadySent is the opening state of a channel if the
// fundingLocked message has successfully been sent to the other peer, // channelReady message has successfully been sent to the other peer,
// but we still haven't announced the channel to the network. // but we still haven't announced the channel to the network.
fundingLockedSent channelReadySent
// addedToRouterGraph is the opening state of a channel if the // addedToRouterGraph is the opening state of a channel if the
// channel has been successfully added to the router graph // channel has been successfully added to the router graph
// immediately after the fundingLocked message has been sent, but // immediately after the channelReady message has been sent, but
// we still haven't announced the channel to the network. // we still haven't announced the channel to the network.
addedToRouterGraph addedToRouterGraph
) )
@@ -604,8 +604,8 @@ func (c channelOpeningState) String() string {
switch c { switch c {
case markedOpen: case markedOpen:
return "markedOpen" return "markedOpen"
case fundingLockedSent: case channelReadySent:
return "fundingLocked" return "channelReady"
case addedToRouterGraph: case addedToRouterGraph:
return "addedToRouterGraph" return "addedToRouterGraph"
default: default:
@@ -617,16 +617,30 @@ func (c channelOpeningState) String() string {
// fundingManager. // fundingManager.
func NewFundingManager(cfg Config) (*Manager, error) { func NewFundingManager(cfg Config) (*Manager, error) {
return &Manager{ return &Manager{
cfg: &cfg, cfg: &cfg,
chanIDKey: cfg.TempChanIDSeed, chanIDKey: cfg.TempChanIDSeed,
activeReservations: make(map[serializedPubKey]pendingChannels), activeReservations: make(
signedReservations: make(map[lnwire.ChannelID][32]byte), map[serializedPubKey]pendingChannels,
newChanBarriers: make(map[lnwire.ChannelID]chan struct{}), ),
fundingMsgs: make(chan *fundingMsg, msgBufferSize), signedReservations: make(
fundingRequests: make(chan *InitFundingMsg, msgBufferSize), map[lnwire.ChannelID][32]byte,
localDiscoverySignals: make(map[lnwire.ChannelID]chan struct{}), ),
handleFundingLockedBarriers: make(map[lnwire.ChannelID]struct{}), newChanBarriers: make(
quit: make(chan struct{}), map[lnwire.ChannelID]chan struct{},
),
fundingMsgs: make(
chan *fundingMsg, msgBufferSize,
),
fundingRequests: make(
chan *InitFundingMsg, msgBufferSize,
),
localDiscoverySignals: make(
map[lnwire.ChannelID]chan struct{},
),
handleChannelReadyBarriers: make(
map[lnwire.ChannelID]struct{},
),
quit: make(chan struct{}),
}, nil }, nil
} }
@@ -889,9 +903,9 @@ func (f *Manager) reservationCoordinator() {
case *lnwire.FundingSigned: case *lnwire.FundingSigned:
f.handleFundingSigned(fmsg.peer, msg) f.handleFundingSigned(fmsg.peer, msg)
case *lnwire.FundingLocked: case *lnwire.ChannelReady:
f.wg.Add(1) f.wg.Add(1)
go f.handleFundingLocked(fmsg.peer, msg) go f.handleChannelReady(fmsg.peer, msg)
case *lnwire.Warning: case *lnwire.Warning:
f.handleWarningMsg(fmsg.peer, msg) f.handleWarningMsg(fmsg.peer, msg)
@@ -1000,38 +1014,38 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
switch channelState { switch channelState {
// The funding transaction was confirmed, but we did not successfully // The funding transaction was confirmed, but we did not successfully
// send the fundingLocked message to the peer, so let's do that now. // send the channelReady message to the peer, so let's do that now.
case markedOpen: case markedOpen:
err := f.sendFundingLocked(channel, lnChannel) err := f.sendChannelReady(channel, lnChannel)
if err != nil { if err != nil {
return fmt.Errorf("failed sending fundingLocked: %v", return fmt.Errorf("failed sending channelReady: %v",
err) err)
} }
// As the fundingLocked message is now sent to the peer, the // As the channelReady message is now sent to the peer, the
// channel is moved to the next state of the state machine. It // channel is moved to the next state of the state machine. It
// will be moved to the last state (actually deleted from the // will be moved to the last state (actually deleted from the
// database) after the channel is finally announced. // database) after the channel is finally announced.
err = f.saveChannelOpeningState( err = f.saveChannelOpeningState(
&channel.FundingOutpoint, fundingLockedSent, &channel.FundingOutpoint, channelReadySent,
shortChanID, shortChanID,
) )
if err != nil { if err != nil {
return fmt.Errorf("error setting channel state to"+ return fmt.Errorf("error setting channel state to"+
" fundingLockedSent: %v", err) " channelReadySent: %v", err)
} }
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+ log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"sent FundingLocked", chanID, shortChanID) "sent ChannelReady", chanID, shortChanID)
return nil return nil
// fundingLocked was sent to peer, but the channel was not added to the // channelReady was sent to peer, but the channel was not added to the
// router graph and the channel announcement was not sent. // router graph and the channel announcement was not sent.
case fundingLockedSent: case channelReadySent:
// We must wait until we've received the peer's funding locked // We must wait until we've received the peer's funding locked
// before sending a channel_update according to BOLT#07. // before sending a channel_update according to BOLT#07.
received, err := f.receivedFundingLocked( received, err := f.receivedChannelReady(
channel.IdentityPub, chanID, channel.IdentityPub, chanID,
) )
if err != nil { if err != nil {
@@ -1040,11 +1054,11 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
} }
if !received { if !received {
// We haven't received FundingLocked, so we'll continue // We haven't received ChannelReady, so we'll continue
// to the next iteration of the loop after sleeping for // to the next iteration of the loop after sleeping for
// checkPeerFundingLockInterval. // checkPeerChannelReadyInterval.
select { select {
case <-time.After(checkPeerFundingLockInterval): case <-time.After(checkPeerChannelReadyInterval):
case <-f.quit: case <-f.quit:
return ErrFundingManagerShuttingDown return ErrFundingManagerShuttingDown
} }
@@ -1054,7 +1068,7 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
var peerAlias *lnwire.ShortChannelID var peerAlias *lnwire.ShortChannelID
if channel.IsZeroConf() { if channel.IsZeroConf() {
// We'll need to wait until funding_locked has been // We'll need to wait until channel_ready has been
// received and the peer lets us know the alias they // received and the peer lets us know the alias they
// want to use for the channel. With this information, // want to use for the channel. With this information,
// we can then construct a ChannelUpdate for them. // we can then construct a ChannelUpdate for them.
@@ -1213,7 +1227,7 @@ func (f *Manager) advancePendingChannelState(
f.cfg.NotifyOpenChannelEvent(channel.FundingOutpoint) f.cfg.NotifyOpenChannelEvent(channel.FundingOutpoint)
// Find and close the discoverySignal for this channel such // Find and close the discoverySignal for this channel such
// that FundingLocked messages will be processed. // that ChannelReady messages will be processed.
chanID := lnwire.NewChanIDFromOutPoint( chanID := lnwire.NewChanIDFromOutPoint(
&channel.FundingOutpoint, &channel.FundingOutpoint,
) )
@@ -2890,10 +2904,10 @@ func (f *Manager) handleFundingConfirmation(
return nil return nil
} }
// sendFundingLocked creates and sends the fundingLocked message. // sendChannelReady creates and sends the channelReady message.
// This should be called after the funding transaction has been confirmed, // This should be called after the funding transaction has been confirmed,
// and the channelState is 'markedOpen'. // and the channelState is 'markedOpen'.
func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel, func (f *Manager) sendChannelReady(completeChan *channeldb.OpenChannel,
channel *lnwallet.LightningChannel) error { channel *lnwallet.LightningChannel) error {
chanID := lnwire.NewChanIDFromOutPoint(&completeChan.FundingOutpoint) chanID := lnwire.NewChanIDFromOutPoint(&completeChan.FundingOutpoint)
@@ -2909,7 +2923,7 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel,
if err != nil { if err != nil {
return fmt.Errorf("unable to create next revocation: %v", err) return fmt.Errorf("unable to create next revocation: %v", err)
} }
fundingLockedMsg := lnwire.NewFundingLocked(chanID, nextRevocation) channelReadyMsg := lnwire.NewChannelReady(chanID, nextRevocation)
// If the channel negotiated the option-scid-alias feature bit, we'll // If the channel negotiated the option-scid-alias feature bit, we'll
// send a TLV segment that includes an alias the peer can use in their // send a TLV segment that includes an alias the peer can use in their
@@ -2926,19 +2940,19 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel,
// We can use a pointer to aliases since GetAliases returns a // We can use a pointer to aliases since GetAliases returns a
// copy of the alias slice. // copy of the alias slice.
fundingLockedMsg.AliasScid = &aliases[0] channelReadyMsg.AliasScid = &aliases[0]
} }
// If the peer has disconnected before we reach this point, we will need // If the peer has disconnected before we reach this point, we will need
// to wait for him to come back online before sending the fundingLocked // to wait for him to come back online before sending the channelReady
// message. This is special for fundingLocked, since failing to send any // message. This is special for channelReady, since failing to send any
// of the previous messages in the funding flow just cancels the flow. // of the previous messages in the funding flow just cancels the flow.
// But now the funding transaction is confirmed, the channel is open // But now the funding transaction is confirmed, the channel is open
// and we have to make sure the peer gets the fundingLocked message when // and we have to make sure the peer gets the channelReady message when
// it comes back online. This is also crucial during restart of lnd, // it comes back online. This is also crucial during restart of lnd,
// where we might try to resend the fundingLocked message before the // where we might try to resend the channelReady message before the
// server has had the time to connect to the peer. We keep trying to // server has had the time to connect to the peer. We keep trying to
// send fundingLocked until we succeed, or the fundingManager is shut // send channelReady until we succeed, or the fundingManager is shut
// down. // down.
for { for {
peer, err := f.waitForPeerOnline(completeChan.IdentityPub) peer, err := f.waitForPeerOnline(completeChan.IdentityPub)
@@ -2956,14 +2970,14 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel,
// We could also refresh the channel state instead of checking // We could also refresh the channel state instead of checking
// whether the feature was negotiated, but this saves us a // whether the feature was negotiated, but this saves us a
// database read. // database read.
if fundingLockedMsg.AliasScid == nil && localAlias && if channelReadyMsg.AliasScid == nil && localAlias &&
remoteAlias { remoteAlias {
// If an alias was not assigned above and the scid // If an alias was not assigned above and the scid
// alias feature was negotiated, check if we already // alias feature was negotiated, check if we already
// have an alias stored in case handleFundingLocked was // have an alias stored in case handleChannelReady was
// called before this. If an alias exists, use that in // called before this. If an alias exists, use that in
// funding_locked. Otherwise, request and store an // channel_ready. Otherwise, request and store an
// alias and use that. // alias and use that.
aliases := f.cfg.AliasManager.GetAliases( aliases := f.cfg.AliasManager.GetAliases(
completeChan.ShortChannelID, completeChan.ShortChannelID,
@@ -2983,31 +2997,31 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel,
return err return err
} }
fundingLockedMsg.AliasScid = &alias channelReadyMsg.AliasScid = &alias
} else { } else {
fundingLockedMsg.AliasScid = &aliases[0] channelReadyMsg.AliasScid = &aliases[0]
} }
} }
log.Infof("Peer(%x) is online, sending FundingLocked "+ log.Infof("Peer(%x) is online, sending ChannelReady "+
"for ChannelID(%v)", peerKey, chanID) "for ChannelID(%v)", peerKey, chanID)
if err := peer.SendMessage(true, fundingLockedMsg); err == nil { if err := peer.SendMessage(true, channelReadyMsg); err == nil {
// Sending succeeded, we can break out and continue the // Sending succeeded, we can break out and continue the
// funding flow. // funding flow.
break break
} }
log.Warnf("Unable to send fundingLocked to peer %x: %v. "+ log.Warnf("Unable to send channelReady to peer %x: %v. "+
"Will retry when online", peerKey, err) "Will retry when online", peerKey, err)
} }
return nil return nil
} }
// receivedFundingLocked checks whether or not we've received a FundingLocked // receivedChannelReady checks whether or not we've received a ChannelReady
// from the remote peer. If we have, RemoteNextRevocation will be set. // from the remote peer. If we have, RemoteNextRevocation will be set.
func (f *Manager) receivedFundingLocked(node *btcec.PublicKey, func (f *Manager) receivedChannelReady(node *btcec.PublicKey,
chanID lnwire.ChannelID) (bool, error) { chanID lnwire.ChannelID) (bool, error) {
// If the funding manager has exited, return an error to stop looping. // If the funding manager has exited, return an error to stop looping.
@@ -3028,7 +3042,7 @@ func (f *Manager) receivedFundingLocked(node *btcec.PublicKey,
channel, err := f.cfg.FindChannel(node, chanID) channel, err := f.cfg.FindChannel(node, chanID)
if err != nil { if err != nil {
log.Errorf("Unable to locate ChannelID(%v) to determine if "+ log.Errorf("Unable to locate ChannelID(%v) to determine if "+
"FundingLocked was received", chanID) "ChannelReady was received", chanID)
return false, err return false, err
} }
@@ -3140,7 +3154,7 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel,
} }
// annAfterSixConfs broadcasts the necessary channel announcement messages to // annAfterSixConfs broadcasts the necessary channel announcement messages to
// the network after 6 confs. Should be called after the fundingLocked message // the network after 6 confs. Should be called after the channelReady message
// is sent and the channel is added to the router graph (channelState is // is sent and the channel is added to the router graph (channelState is
// 'addedToRouterGraph') and the channel is ready to be used. This is the last // 'addedToRouterGraph') and the channel is ready to be used. This is the last
// step in the channel opening process, and the opening state will be deleted // step in the channel opening process, and the opening state will be deleted
@@ -3391,36 +3405,36 @@ func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel,
return nil return nil
} }
// handleFundingLocked finalizes the channel funding process and enables the // handleChannelReady finalizes the channel funding process and enables the
// channel to enter normal operating mode. // channel to enter normal operating mode.
func (f *Manager) handleFundingLocked(peer lnpeer.Peer, func (f *Manager) handleChannelReady(peer lnpeer.Peer,
msg *lnwire.FundingLocked) { msg *lnwire.ChannelReady) {
defer f.wg.Done() defer f.wg.Done()
log.Debugf("Received FundingLocked for ChannelID(%v) from "+ log.Debugf("Received ChannelReady for ChannelID(%v) from "+
"peer %x", msg.ChanID, "peer %x", msg.ChanID,
peer.IdentityKey().SerializeCompressed()) peer.IdentityKey().SerializeCompressed())
// If we are currently in the process of handling a funding locked // If we are currently in the process of handling a funding locked
// message for this channel, ignore. // message for this channel, ignore.
f.handleFundingLockedMtx.Lock() f.handleChannelReadyMtx.Lock()
_, ok := f.handleFundingLockedBarriers[msg.ChanID] _, ok := f.handleChannelReadyBarriers[msg.ChanID]
if ok { if ok {
log.Infof("Already handling fundingLocked for "+ log.Infof("Already handling channelReady for "+
"ChannelID(%v), ignoring.", msg.ChanID) "ChannelID(%v), ignoring.", msg.ChanID)
f.handleFundingLockedMtx.Unlock() f.handleChannelReadyMtx.Unlock()
return return
} }
// If not already handling fundingLocked for this channel, set up // If not already handling channelReady for this channel, set up
// barrier, and move on. // barrier, and move on.
f.handleFundingLockedBarriers[msg.ChanID] = struct{}{} f.handleChannelReadyBarriers[msg.ChanID] = struct{}{}
f.handleFundingLockedMtx.Unlock() f.handleChannelReadyMtx.Unlock()
defer func() { defer func() {
f.handleFundingLockedMtx.Lock() f.handleChannelReadyMtx.Lock()
delete(f.handleFundingLockedBarriers, msg.ChanID) delete(f.handleChannelReadyBarriers, msg.ChanID)
f.handleFundingLockedMtx.Unlock() f.handleChannelReadyMtx.Unlock()
}() }()
f.localDiscoveryMtx.Lock() f.localDiscoveryMtx.Lock()
@@ -3464,7 +3478,7 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer,
// during invoice creation. In the zero-conf case, it is also used to // during invoice creation. In the zero-conf case, it is also used to
// provide a ChannelUpdate to the remote peer. This is done before the // provide a ChannelUpdate to the remote peer. This is done before the
// call to InsertNextRevocation in case the call to PutPeerAlias fails. // call to InsertNextRevocation in case the call to PutPeerAlias fails.
// If it were to fail on the first call to handleFundingLocked, we // If it were to fail on the first call to handleChannelReady, we
// wouldn't want the channel to be usable yet. // wouldn't want the channel to be usable yet.
if channel.NegotiatedAliasFeature() { if channel.NegotiatedAliasFeature() {
// If the AliasScid field is nil, we must fail out. We will // If the AliasScid field is nil, we must fail out. We will
@@ -3487,14 +3501,14 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer,
// If we do not have an alias stored, we'll create one now. // If we do not have an alias stored, we'll create one now.
// This is only used in the upgrade case where a user toggles // This is only used in the upgrade case where a user toggles
// the option-scid-alias feature-bit to on. We'll also send the // the option-scid-alias feature-bit to on. We'll also send the
// funding_locked message here in case the link is created // channel_ready message here in case the link is created
// before sendFundingLocked is called. // before sendChannelReady is called.
aliases := f.cfg.AliasManager.GetAliases( aliases := f.cfg.AliasManager.GetAliases(
channel.ShortChannelID, channel.ShortChannelID,
) )
if len(aliases) == 0 { if len(aliases) == 0 {
// No aliases were found so we'll request and store an // No aliases were found so we'll request and store an
// alias and use it in the funding_locked message. // alias and use it in the channel_ready message.
alias, err := f.cfg.AliasManager.RequestAlias() alias, err := f.cfg.AliasManager.RequestAlias()
if err != nil { if err != nil {
log.Errorf("unable to request alias: %v", err) log.Errorf("unable to request alias: %v", err)
@@ -3517,12 +3531,12 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer,
return return
} }
fundingLockedMsg := lnwire.NewFundingLocked( channelReadyMsg := lnwire.NewChannelReady(
chanID, secondPoint, chanID, secondPoint,
) )
fundingLockedMsg.AliasScid = &alias channelReadyMsg.AliasScid = &alias
err = peer.SendMessage(true, fundingLockedMsg) err = peer.SendMessage(true, channelReadyMsg)
if err != nil { if err != nil {
log.Errorf("unable to send funding locked: %v", log.Errorf("unable to send funding locked: %v",
err) err)
@@ -3532,16 +3546,16 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer,
} }
// If the RemoteNextRevocation is non-nil, it means that we have // If the RemoteNextRevocation is non-nil, it means that we have
// already processed fundingLocked for this channel, so ignore. This // already processed channelReady for this channel, so ignore. This
// check is after the alias logic so we store the peer's most recent // check is after the alias logic so we store the peer's most recent
// alias. The spec requires us to validate that subsequent // alias. The spec requires us to validate that subsequent
// funding_locked messages use the same per commitment point (the // channel_ready messages use the same per commitment point (the
// second), but it is not actually necessary since we'll just end up // second), but it is not actually necessary since we'll just end up
// ignoring it. We are, however, required to *send* the same per // ignoring it. We are, however, required to *send* the same per
// commitment point, since another pedantic implementation might // commitment point, since another pedantic implementation might
// verify it. // verify it.
if channel.RemoteNextRevocation != nil { if channel.RemoteNextRevocation != nil {
log.Infof("Received duplicate fundingLocked for "+ log.Infof("Received duplicate channelReady for "+
"ChannelID(%v), ignoring.", chanID) "ChannelID(%v), ignoring.", chanID)
return return
} }
+180 -160
View File
@@ -900,8 +900,8 @@ func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
sentMsg, ok = msg.(*lnwire.FundingCreated) sentMsg, ok = msg.(*lnwire.FundingCreated)
case "FundingSigned": case "FundingSigned":
sentMsg, ok = msg.(*lnwire.FundingSigned) sentMsg, ok = msg.(*lnwire.FundingSigned)
case "FundingLocked": case "ChannelReady":
sentMsg, ok = msg.(*lnwire.FundingLocked) sentMsg, ok = msg.(*lnwire.ChannelReady)
case "Error": case "Error":
sentMsg, ok = msg.(*lnwire.Error) sentMsg, ok = msg.(*lnwire.Error)
default: default:
@@ -1050,13 +1050,13 @@ func assertMarkedOpen(t *testing.T, alice, bob *testNode,
assertDatabaseState(t, bob, fundingOutPoint, markedOpen) assertDatabaseState(t, bob, fundingOutPoint, markedOpen)
} }
func assertFundingLockedSent(t *testing.T, alice, bob *testNode, func assertChannelReadySent(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) { fundingOutPoint *wire.OutPoint) {
t.Helper() t.Helper()
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
} }
func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode, func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode,
@@ -1082,7 +1082,7 @@ func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
t.Helper() t.Helper()
// After the FundingLocked message is sent, Alice and Bob will each send // After the ChannelReady message is sent, Alice and Bob will each send
// the following messages to their gossiper: // the following messages to their gossiper:
// 1) ChannelAnnouncement // 1) ChannelAnnouncement
// 2) ChannelUpdate // 2) ChannelUpdate
@@ -1222,7 +1222,7 @@ func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
func assertAnnouncementSignatures(t *testing.T, alice, bob *testNode) { func assertAnnouncementSignatures(t *testing.T, alice, bob *testNode) {
t.Helper() t.Helper()
// After the FundingLocked message is sent and six confirmations have // After the ChannelReady message is sent and six confirmations have
// been reached, the channel will be announced to the greater network // been reached, the channel will be announced to the greater network
// by having the nodes exchange announcement signatures. // by having the nodes exchange announcement signatures.
// Two distinct messages will be sent: // Two distinct messages will be sent:
@@ -1347,7 +1347,7 @@ func assertInitialFwdingPolicyNotFound(t *testing.T, node *testNode,
fwdingPolicy) fwdingPolicy)
} }
func assertHandleFundingLocked(t *testing.T, alice, bob *testNode) { func assertHandleChannelReady(t *testing.T, alice, bob *testNode) {
t.Helper() t.Helper()
// They should both send the new channel state to their peer. // They should both send the new channel state to their peer.
@@ -1405,26 +1405,28 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -1635,7 +1637,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
) )
// After the funding transaction gets mined, both nodes will send the // After the funding transaction gets mined, both nodes will send the
// fundingLocked message to the other peer. If the funding node fails // channelReady message to the other peer. If the funding node fails
// before this message has been successfully sent, it should retry // before this message has been successfully sent, it should retry
// sending it on restart. We mimic this behavior by letting the // sending it on restart. We mimic this behavior by letting the
// SendToPeer method return an error, as if the message was not // SendToPeer method return an error, as if the message was not
@@ -1666,7 +1668,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction was mined, Bob should have successfully // After the funding transaction was mined, Bob should have successfully
// sent the fundingLocked message, while Alice failed sending it. In // sent the channelReady message, while Alice failed sending it. In
// Alice's case this means that there should be no messages for Bob, and // Alice's case this means that there should be no messages for Bob, and
// the channel should still be in state 'markedOpen' // the channel should still be in state 'markedOpen'
select { select {
@@ -1677,18 +1679,19 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
} }
// Bob will send funding locked to Alice. // Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Alice should still be markedOpen // Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen) assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent fundingLocked. // While Bob successfully sent channelReady.
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
// We now recreate Alice's fundingManager with the correct sendMessage // We now recreate Alice's fundingManager with the correct sendMessage
// implementation, and expect it to retry sending the fundingLocked // implementation, and expect it to retry sending the channelReady
// message. We'll explicitly shut down Alice's funding manager to // message. We'll explicitly shut down Alice's funding manager to
// prevent a race when overriding the sendMessage implementation. // prevent a race when overriding the sendMessage implementation.
if err := alice.fundingMgr.Stop(); err != nil { if err := alice.fundingMgr.Stop(); err != nil {
@@ -1706,12 +1709,13 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
return errChan return errChan
} }
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// The state should now be fundingLockedSent // The state should now be channelReadySent
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
// Check that the channel announcements were never sent // Check that the channel announcements were never sent
select { select {
@@ -1722,13 +1726,13 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
// Expected // Expected
} }
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Next up, we check that Alice rebroadcasts the announcement // Next up, we check that Alice rebroadcasts the announcement
// messages on restart. Bob should as expected send announcements. // messages on restart. Bob should as expected send announcements.
@@ -1771,7 +1775,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
// TestFundingManagerOfflinePeer checks that the fundingManager waits for the // TestFundingManagerOfflinePeer checks that the fundingManager waits for the
// server to notify when the peer comes online, in case sending the // server to notify when the peer comes online, in case sending the
// fundingLocked message fails the first time. // channelReady message fails the first time.
func TestFundingManagerOfflinePeer(t *testing.T) { func TestFundingManagerOfflinePeer(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1791,8 +1795,8 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
) )
// After the funding transaction gets mined, both nodes will send the // After the funding transaction gets mined, both nodes will send the
// fundingLocked message to the other peer. If the funding node fails // channelReady message to the other peer. If the funding node fails
// to send the fundingLocked message to the peer, it should wait for // to send the channelReady message to the peer, it should wait for
// the server to notify it that the peer is back online, and try again. // the server to notify it that the peer is back online, and try again.
// We'll save the current implementation of sendMessage to restore the // We'll save the current implementation of sendMessage to restore the
// original behavior later on. // original behavior later on.
@@ -1823,7 +1827,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction was mined, Bob should have successfully // After the funding transaction was mined, Bob should have successfully
// sent the fundingLocked message, while Alice failed sending it. In // sent the channelReady message, while Alice failed sending it. In
// Alice's case this means that there should be no messages for Bob, and // Alice's case this means that there should be no messages for Bob, and
// the channel should still be in state 'markedOpen' // the channel should still be in state 'markedOpen'
select { select {
@@ -1834,15 +1838,16 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
} }
// Bob will send funding locked to Alice // Bob will send funding locked to Alice
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Alice should still be markedOpen // Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen) assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent fundingLocked. // While Bob successfully sent channelReady.
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
// Alice should be waiting for the server to notify when Bob comes back // Alice should be waiting for the server to notify when Bob comes back
// online. // online.
@@ -1868,7 +1873,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
} }
// Before sending on the con chan, update Alice's NotifyWhenOnline // Before sending on the con chan, update Alice's NotifyWhenOnline
// function so that the next invocation in receivedFundingLocked will // function so that the next invocation in receivedChannelReady will
// use this new function. // use this new function.
alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte, alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte,
connected chan<- lnpeer.Peer) { connected chan<- lnpeer.Peer) {
@@ -1881,21 +1886,22 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
bob.sendMessage = workingSendMessage bob.sendMessage = workingSendMessage
con <- bob con <- bob
// This should make Alice send the fundingLocked. // This should make Alice send the channelReady.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// The state should now be fundingLockedSent // The state should now be channelReadySent
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent) assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -2272,10 +2278,10 @@ func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
assertNumPendingChannelsBecomes(t, bob, 0) assertNumPendingChannelsBecomes(t, bob, 0)
} }
// TestFundingManagerReceiveFundingLockedTwice checks that the fundingManager // TestFundingManagerReceiveChannelReadyTwice checks that the fundingManager
// continues to operate as expected in case we receive a duplicate fundingLocked // continues to operate as expected in case we receive a duplicate channelReady
// message. // message.
func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) { func TestFundingManagerReceiveChannelReadyTwice(t *testing.T) {
t.Parallel() t.Parallel()
alice, bob := setupFundingManagers(t) alice, bob := setupFundingManagers(t)
@@ -2310,27 +2316,29 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Send the fundingLocked message twice to Alice, and once to Bob. // Send the channelReady message twice to Alice, and once to Bob.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Alice should not send the channel state the second time, as the // Alice should not send the channel state the second time, as the
// second funding locked should just be ignored. // second funding locked should just be ignored.
@@ -2341,9 +2349,9 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
// Expected // Expected
} }
// Another fundingLocked should also be ignored, since Alice should // Another channelReady should also be ignored, since Alice should
// have updated her database at this point. // have updated her database at this point.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
select { select {
case <-alice.newChannels: case <-alice.newChannels:
t.Fatalf("alice sent new channel to peer a second time") t.Fatalf("alice sent new channel to peer a second time")
@@ -2384,7 +2392,7 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
} }
// TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly // TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly
// handles receiving a fundingLocked after the its own fundingLocked and channel // handles receiving a channelReady after the its own channelReady and channel
// announcement is sent and gets restarted. // announcement is sent and gets restarted.
func TestFundingManagerRestartAfterChanAnn(t *testing.T) { func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
t.Parallel() t.Parallel()
@@ -2421,26 +2429,28 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -2454,7 +2464,7 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
waitForOpenUpdate(t, updateChan) waitForOpenUpdate(t, updateChan)
// At this point we restart Alice's fundingManager, before she receives // At this point we restart Alice's fundingManager, before she receives
// the fundingLocked message. After restart, she will receive it, and // the channelReady message. After restart, she will receive it, and
// we expect her to be able to handle it correctly. // we expect her to be able to handle it correctly.
recreateAliceFundingManager(t, alice) recreateAliceFundingManager(t, alice)
@@ -2480,10 +2490,10 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint) assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
} }
// TestFundingManagerRestartAfterReceivingFundingLocked checks that the // TestFundingManagerRestartAfterReceivingChannelReady checks that the
// fundingManager continues to operate as expected after it has received // fundingManager continues to operate as expected after it has received
// fundingLocked and then gets restarted. // channelReady and then gets restarted.
func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) { func TestFundingManagerRestartAfterReceivingChannelReady(t *testing.T) {
t.Parallel() t.Parallel()
alice, bob := setupFundingManagers(t) alice, bob := setupFundingManagers(t)
@@ -2518,28 +2528,30 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Let Alice immediately get the fundingLocked message. // Let Alice immediately get the channelReady message.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
// Also let Bob get the fundingLocked message. // Also let Bob get the channelReady message.
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// At this point we restart Alice's fundingManager. // At this point we restart Alice's fundingManager.
recreateAliceFundingManager(t, alice) recreateAliceFundingManager(t, alice)
@@ -2611,26 +2623,28 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -2734,26 +2748,28 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
assertMarkedOpen(t, alice, bob, fundingOutPoint) assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly // Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint) assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -3169,23 +3185,25 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
} }
// After the funding transaction is mined, Alice will send // After the funding transaction is mined, Alice will send
// fundingLocked to Bob. // channelReady to Bob.
fundingLockedAlice := assertFundingMsgSent( channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send funding locked to Alice. // And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent( channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.True(t, ok)
// Exchange the fundingLocked messages. // Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob) alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice) bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new // Check that they notify the breach arbiter and peer about the new
// channel. // channel.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel // Make sure both fundingManagers send the expected channel
// announcements. // announcements.
@@ -3486,14 +3504,14 @@ func TestFundingManagerMaxPendingChannels(t *testing.T) {
Tx: txs[i], Tx: txs[i],
} }
// Expect both to be sending FundingLocked. // Expect both to be sending ChannelReady.
_ = assertFundingMsgSent( _ = assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
_ = assertFundingMsgSent( _ = assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
} }
@@ -4202,26 +4220,28 @@ func TestFundingManagerZeroConf(t *testing.T) {
Index: 0, Index: 0,
} }
// Assert that Bob's funding_locked message has an AliasScid. // Assert that Bob's channel_ready message has an AliasScid.
bobFundingLocked := assertFundingMsgSent( bobChannelReady, ok := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked", t, bob.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.NotNil(t, bobFundingLocked.AliasScid) require.True(t, ok)
require.Equal(t, *bobFundingLocked.AliasScid, alias) require.NotNil(t, bobChannelReady.AliasScid)
require.Equal(t, *bobChannelReady.AliasScid, alias)
// Do the same for Alice as well. // Do the same for Alice as well.
aliceFundingLocked := assertFundingMsgSent( aliceChannelReady, ok := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked", t, alice.msgChan, "ChannelReady",
).(*lnwire.FundingLocked) ).(*lnwire.ChannelReady)
require.NotNil(t, aliceFundingLocked.AliasScid) require.True(t, ok)
require.Equal(t, *aliceFundingLocked.AliasScid, alias) require.NotNil(t, aliceChannelReady.AliasScid)
require.Equal(t, *aliceChannelReady.AliasScid, alias)
// Exchange the funding_locked messages. // Exchange the channel_ready messages.
alice.fundingMgr.ProcessFundingMsg(bobFundingLocked, bob) alice.fundingMgr.ProcessFundingMsg(bobChannelReady, bob)
bob.fundingMgr.ProcessFundingMsg(aliceFundingLocked, alice) bob.fundingMgr.ProcessFundingMsg(aliceChannelReady, alice)
// We'll assert that they both create new links. // We'll assert that they both create new links.
assertHandleFundingLocked(t, alice, bob) assertHandleChannelReady(t, alice, bob)
// We'll now assert that both sides send ChannelAnnouncement and // We'll now assert that both sides send ChannelAnnouncement and
// ChannelUpdate messages. // ChannelUpdate messages.
+4 -3
View File
@@ -6,10 +6,10 @@ require (
github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f
github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.3 github.com/btcsuite/btcd/btcutil v1.1.3
github.com/btcsuite/btcd/btcutil/psbt v1.1.5 github.com/btcsuite/btcd/btcutil/psbt v1.1.8
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcwallet v0.16.7 github.com/btcsuite/btcwallet v0.16.8-0.20230324081040-520f650ce045
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 github.com/btcsuite/btcwallet/wallet/txrules v1.2.0
github.com/btcsuite/btcwallet/walletdb v1.4.0 github.com/btcsuite/btcwallet/walletdb v1.4.0
@@ -52,8 +52,9 @@ require (
go.etcd.io/etcd/client/v3 v3.5.7 go.etcd.io/etcd/client/v3 v3.5.7
golang.org/x/crypto v0.1.0 golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20221111094246-ab4555d3164f golang.org/x/exp v0.0.0-20221111094246-ab4555d3164f
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/net v0.4.0 golang.org/x/net v0.4.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
golang.org/x/term v0.3.0 golang.org/x/term v0.3.0
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
google.golang.org/grpc v1.41.0 google.golang.org/grpc v1.41.0
+7 -5
View File
@@ -80,8 +80,8 @@ github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUB
github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34=
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
github.com/btcsuite/btcd/btcutil/psbt v1.1.5 h1:x0ZRrYY8j75ThV6xBz86CkYAG82F5bzay4H5D1c8b/U= github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg=
github.com/btcsuite/btcd/btcutil/psbt v1.1.5/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
@@ -89,8 +89,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/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 h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 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/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.16.7 h1:J6nBMMMc90n77/4QIfzRFn5XB1hPvMDfcgX5U6Ls0kI= github.com/btcsuite/btcwallet v0.16.8-0.20230324081040-520f650ce045 h1:302lmdYbONzrLACkm9IlCobMYuYOjF8to+tUhaQsTzc=
github.com/btcsuite/btcwallet v0.16.7/go.mod h1:J/q3/JxytAcuqR+zSTCRZ5K+0LtMuhxtCjLVXKDHBu0= github.com/btcsuite/btcwallet v0.16.8-0.20230324081040-520f650ce045/go.mod h1:WBcb0CNEgzjF2EdatcERzG7cd/lhHXtNJ4VjIePOdXM=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0=
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg=
@@ -654,6 +654,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/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-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 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= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
@@ -721,8 +722,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+23 -19
View File
@@ -710,6 +710,9 @@ func (l *channelLink) syncChanStates() error {
// first message sent MUST be the ChanSync message. // first message sent MUST be the ChanSync message.
select { select {
case msg := <-l.upstream: case msg := <-l.upstream:
l.log.Tracef("Received msg=%v from peer(%x)", msg.MsgType(),
l.cfg.Peer.PubKey())
remoteChanSyncMsg, ok := msg.(*lnwire.ChannelReestablish) remoteChanSyncMsg, ok := msg.(*lnwire.ChannelReestablish)
if !ok { if !ok {
return fmt.Errorf("first message sent to sync "+ return fmt.Errorf("first message sent to sync "+
@@ -721,12 +724,12 @@ func (l *channelLink) syncChanStates() error {
// done any state updates yet, then we'll retransmit the // done any state updates yet, then we'll retransmit the
// funding locked message first. We do this, as at this point // funding locked message first. We do this, as at this point
// we can't be sure if they've really received the // we can't be sure if they've really received the
// FundingLocked message. // ChannelReady message.
if remoteChanSyncMsg.NextLocalCommitHeight == 1 && if remoteChanSyncMsg.NextLocalCommitHeight == 1 &&
localChanSyncMsg.NextLocalCommitHeight == 1 && localChanSyncMsg.NextLocalCommitHeight == 1 &&
!l.channel.IsPending() { !l.channel.IsPending() {
l.log.Infof("resending FundingLocked message to peer") l.log.Infof("resending ChannelReady message to peer")
nextRevocation, err := l.channel.NextRevocationKey() nextRevocation, err := l.channel.NextRevocationKey()
if err != nil { if err != nil {
@@ -734,13 +737,13 @@ func (l *channelLink) syncChanStates() error {
"revocation: %v", err) "revocation: %v", err)
} }
fundingLockedMsg := lnwire.NewFundingLocked( channelReadyMsg := lnwire.NewChannelReady(
l.ChanID(), nextRevocation, l.ChanID(), nextRevocation,
) )
// For channels that negotiated the option-scid-alias // For channels that negotiated the option-scid-alias
// feature bit, ensure that we send over the alias in // feature bit, ensure that we send over the alias in
// the funding_locked message. We'll send the first // the channel_ready message. We'll send the first
// alias we find for the channel since it does not // alias we find for the channel since it does not
// matter which alias we send. We'll error out if no // matter which alias we send. We'll error out if no
// aliases are found. // aliases are found.
@@ -756,13 +759,13 @@ func (l *channelLink) syncChanStates() error {
// getAliases returns a copy of the alias slice // getAliases returns a copy of the alias slice
// so it is ok to use a pointer to the first // so it is ok to use a pointer to the first
// entry. // entry.
fundingLockedMsg.AliasScid = &aliases[0] channelReadyMsg.AliasScid = &aliases[0]
} }
err = l.cfg.Peer.SendMessage(false, fundingLockedMsg) err = l.cfg.Peer.SendMessage(false, channelReadyMsg)
if err != nil { if err != nil {
return fmt.Errorf("unable to re-send "+ return fmt.Errorf("unable to re-send "+
"FundingLocked: %v", err) "ChannelReady: %v", err)
} }
} }
@@ -2551,17 +2554,8 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
policy := l.cfg.FwrdingPolicy policy := l.cfg.FwrdingPolicy
l.RUnlock() l.RUnlock()
// First check whether the outgoing htlc satisfies the channel policy. // Using the amount of the incoming HTLC, we'll calculate the expected
err := l.canSendHtlc( // fee this incoming HTLC must carry in order to satisfy the
policy, payHash, amtToForward, outgoingTimeout, heightNow,
originalScid,
)
if err != nil {
return err
}
// Next, using the amount of the incoming HTLC, we'll calculate the
// expected fee this incoming HTLC must carry in order to satisfy the
// constraints of the outgoing link. // constraints of the outgoing link.
expectedFee := ExpectedFee(policy, amtToForward) expectedFee := ExpectedFee(policy, amtToForward)
@@ -2569,7 +2563,8 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
// this HTLC as it didn't provide a sufficient amount of fees, or the // this HTLC as it didn't provide a sufficient amount of fees, or the
// values have been tampered with, or the send used incorrect/dated // values have been tampered with, or the send used incorrect/dated
// information to construct the forwarding information for this hop. In // information to construct the forwarding information for this hop. In
// any case, we'll cancel this HTLC. // any case, we'll cancel this HTLC. We're checking for this case first
// to leak as little information as possible.
actualFee := incomingHtlcAmt - amtToForward actualFee := incomingHtlcAmt - amtToForward
if incomingHtlcAmt < amtToForward || actualFee < expectedFee { if incomingHtlcAmt < amtToForward || actualFee < expectedFee {
l.log.Warnf("outgoing htlc(%x) has insufficient fee: "+ l.log.Warnf("outgoing htlc(%x) has insufficient fee: "+
@@ -2585,6 +2580,15 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
return NewLinkError(failure) return NewLinkError(failure)
} }
// Check whether the outgoing htlc satisfies the channel policy.
err := l.canSendHtlc(
policy, payHash, amtToForward, outgoingTimeout, heightNow,
originalScid,
)
if err != nil {
return err
}
// Finally, we'll ensure that the time-lock on the outgoing HTLC meets // Finally, we'll ensure that the time-lock on the outgoing HTLC meets
// the following constraint: the incoming time-lock minus our time-lock // the following constraint: the incoming time-lock minus our time-lock
// delta should equal the outgoing time lock. Otherwise, whether the // delta should equal the outgoing time lock. Otherwise, whether the
+25 -12
View File
@@ -1684,8 +1684,8 @@ func TestChannelLinkSingleHopMessageOrdering(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
{"alice", "bob", &lnwire.UpdateAddHTLC{}, false}, {"alice", "bob", &lnwire.UpdateAddHTLC{}, false},
{"alice", "bob", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false},
@@ -3303,8 +3303,8 @@ func TestChannelRetransmission(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
// Send payment from Alice to Bob and intercept // Send payment from Alice to Bob and intercept
// the last revocation message, in this case // the last revocation message, in this case
@@ -3343,8 +3343,8 @@ func TestChannelRetransmission(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
// Send payment from Alice to Bob and intercept // Send payment from Alice to Bob and intercept
// the last revocation message, in this case // the last revocation message, in this case
@@ -3385,8 +3385,8 @@ func TestChannelRetransmission(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
// Attempt make a payment from Alice to Bob, // Attempt make a payment from Alice to Bob,
// which is intercepted, emulating the Bob // which is intercepted, emulating the Bob
@@ -3399,8 +3399,8 @@ func TestChannelRetransmission(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
// After Bob has notified Alice that he didn't // After Bob has notified Alice that he didn't
// receive updates Alice should re-send them. // receive updates Alice should re-send them.
@@ -3816,8 +3816,8 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) {
{"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.ChannelReestablish{}, false},
{"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false},
{"alice", "bob", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.ChannelReady{}, false},
{"bob", "alice", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.ChannelReady{}, false},
// First fee update. // First fee update.
{"alice", "bob", &lnwire.UpdateFee{}, false}, {"alice", "bob", &lnwire.UpdateFee{}, false},
@@ -5643,6 +5643,19 @@ func TestCheckHtlcForward(t *testing.T) {
} }
}) })
// Test that insufficient fee error takes preference over insufficient
// channel balance.
t.Run("insufficient fee error preference", func(t *testing.T) {
t.Parallel()
result := link.CheckHtlcForward(
hash, 100005, 100000, 200,
150, 0, lnwire.ShortChannelID{},
)
_, ok := result.WireMessage().(*lnwire.FailFeeInsufficient)
require.True(t, ok, "expected FailFeeInsufficient failure code")
})
t.Run("expiry too soon", func(t *testing.T) { t.Run("expiry too soon", func(t *testing.T) {
result := link.CheckHtlcForward(hash, 1500, 1000, result := link.CheckHtlcForward(hash, 1500, 1000,
200, 150, 190, lnwire.ShortChannelID{}) 200, 150, 190, lnwire.ShortChannelID{})
+1 -1
View File
@@ -627,7 +627,7 @@ func (s *mockServer) readHandler(message lnwire.Message) error {
targetChan = msg.ChanID targetChan = msg.ChanID
case *lnwire.CommitSig: case *lnwire.CommitSig:
targetChan = msg.ChanID targetChan = msg.ChanID
case *lnwire.FundingLocked: case *lnwire.ChannelReady:
// Ignore // Ignore
return nil return nil
case *lnwire.ChannelReestablish: case *lnwire.ChannelReestablish:
+2 -1
View File
@@ -82,7 +82,7 @@ func TestSwitchAddDuplicateLink(t *testing.T) {
// TestSwitchHasActiveLink tests the behavior of HasActiveLink, and asserts that // TestSwitchHasActiveLink tests the behavior of HasActiveLink, and asserts that
// it only returns true if a link's short channel id has confirmed (meaning the // it only returns true if a link's short channel id has confirmed (meaning the
// channel is no longer pending) and it's EligibleToForward method returns true, // channel is no longer pending) and it's EligibleToForward method returns true,
// i.e. it has received FundingLocked from the remote peer. // i.e. it has received ChannelReady from the remote peer.
func TestSwitchHasActiveLink(t *testing.T) { func TestSwitchHasActiveLink(t *testing.T) {
t.Parallel() t.Parallel()
@@ -5339,6 +5339,7 @@ func testSwitchHandlePacketForward(t *testing.T, zeroConf, private,
ogPacket := &htlcPacket{ ogPacket := &htlcPacket{
incomingChanID: bobLink.ShortChanID(), incomingChanID: bobLink.ShortChanID(),
incomingHTLCID: 0, incomingHTLCID: 0,
incomingAmount: 1000,
obfuscator: NewMockObfuscator(), obfuscator: NewMockObfuscator(),
htlc: &lnwire.UpdateAddHTLC{ htlc: &lnwire.UpdateAddHTLC{
PaymentHash: rhash, PaymentHash: rhash,
+1 -1
View File
@@ -495,7 +495,7 @@ func getChanID(msg lnwire.Message) (lnwire.ChannelID, error) {
chanID = msg.ChanID chanID = msg.ChanID
case *lnwire.ChannelReestablish: case *lnwire.ChannelReestablish:
chanID = msg.ChanID chanID = msg.ChanID
case *lnwire.FundingLocked: case *lnwire.ChannelReady:
chanID = msg.ChanID chanID = msg.ChanID
case *lnwire.UpdateFee: case *lnwire.UpdateFee:
chanID = msg.ChanID chanID = msg.ChanID
+4
View File
@@ -515,4 +515,8 @@ var allTestCases = []*lntest.TestCase{
Name: "lookup htlc resolution", Name: "lookup htlc resolution",
TestFunc: testLookupHtlcResolution, TestFunc: testLookupHtlcResolution,
}, },
{
Name: "watchtower session management",
TestFunc: testWatchtowerSessionManagement,
},
} }
+1
View File
@@ -24,6 +24,7 @@ func testLookupHtlcResolution(ht *lntest.HarnessTest) {
cp := ht.OpenChannel( cp := ht.OpenChannel(
alice, carol, lntest.OpenChannelParams{Amt: chanAmt}, alice, carol, lntest.OpenChannelParams{Amt: chanAmt},
) )
defer ht.CloseChannel(alice, cp)
// Channel should be ready for payments. // Channel should be ready for payments.
const payAmt = 100 const payAmt = 100
+94 -51
View File
@@ -6,6 +6,8 @@ import (
"io/ioutil" "io/ioutil"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
@@ -757,10 +759,8 @@ func testAbandonChannel(ht *lntest.HarnessTest) {
// testSweepAllCoins tests that we're able to properly sweep all coins from the // testSweepAllCoins tests that we're able to properly sweep all coins from the
// wallet into a single target address at the specified fee rate. // wallet into a single target address at the specified fee rate.
//
// TODO(yy): expand this test to also use P2TR.
func testSweepAllCoins(ht *lntest.HarnessTest) { func testSweepAllCoins(ht *lntest.HarnessTest) {
// First, we'll make a new node, ainz who'll we'll use to test wallet // First, we'll make a new node, Ainz who'll we'll use to test wallet
// sweeping. // sweeping.
// //
// NOTE: we won't use standby nodes here since the test will change // NOTE: we won't use standby nodes here since the test will change
@@ -772,60 +772,50 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz) ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz)
ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, ainz) ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, ainz)
// Ensure that we can't send coins to our own Pubkey. // Create a label that will be used to label the transaction with.
info := ainz.RPC.GetInfo()
// Create a label that we will used to label the transaction with.
sendCoinsLabel := "send all coins" sendCoinsLabel := "send all coins"
sweepReq := &lnrpc.SendCoinsRequest{ // Ensure that we can't send coins to our own Pubkey.
Addr: info.IdentityPubkey, ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ainz.RPC.GetInfo().IdentityPubkey,
SendAll: true, SendAll: true,
Label: sendCoinsLabel, Label: sendCoinsLabel,
} })
ainz.RPC.SendCoinsAssertErr(sweepReq)
// Ensure that we can't send coins to another user's Pubkey. // Ensure that we can't send coins to another user's Pubkey.
info = ht.Alice.RPC.GetInfo() ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ht.Alice.RPC.GetInfo().IdentityPubkey,
sweepReq = &lnrpc.SendCoinsRequest{
Addr: info.IdentityPubkey,
SendAll: true, SendAll: true,
Label: sendCoinsLabel, Label: sendCoinsLabel,
} })
ainz.RPC.SendCoinsAssertErr(sweepReq)
// With the two coins above mined, we'll now instruct ainz to sweep all // With the two coins above mined, we'll now instruct Ainz to sweep all
// the coins to an external address not under its control. We will // the coins to an external address not under its control. We will first
// first attempt to send the coins to addresses that are not compatible // attempt to send the coins to addresses that are not compatible
// with the current network. This is to test that the wallet will // with the current network. This is to test that the wallet will
// prevent any onchain transactions to addresses that are not on the // prevent any on-chain transactions to addresses that are not on the
// same network as the user. // same network as the user.
// Send coins to a testnet3 address. // Send coins to a testnet3 address.
sweepReq = &lnrpc.SendCoinsRequest{ ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: "tb1qfc8fusa98jx8uvnhzavxccqlzvg749tvjw82tg", Addr: "tb1qfc8fusa98jx8uvnhzavxccqlzvg749tvjw82tg",
SendAll: true, SendAll: true,
Label: sendCoinsLabel, Label: sendCoinsLabel,
} })
ainz.RPC.SendCoinsAssertErr(sweepReq)
// Send coins to a mainnet address. // Send coins to a mainnet address.
sweepReq = &lnrpc.SendCoinsRequest{ ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: "1MPaXKp5HhsLNjVSqaL7fChE3TVyrTMRT3", Addr: "1MPaXKp5HhsLNjVSqaL7fChE3TVyrTMRT3",
SendAll: true, SendAll: true,
Label: sendCoinsLabel, Label: sendCoinsLabel,
} })
ainz.RPC.SendCoinsAssertErr(sweepReq)
// Send coins to a compatible address. // Send coins to a compatible address.
minerAddr := ht.Miner.NewMinerAddress() ainz.RPC.SendCoins(&lnrpc.SendCoinsRequest{
sweepReq = &lnrpc.SendCoinsRequest{ Addr: ht.Miner.NewMinerAddress().String(),
Addr: minerAddr.String(),
SendAll: true, SendAll: true,
Label: sendCoinsLabel, Label: sendCoinsLabel,
} })
ainz.RPC.SendCoins(sweepReq)
// We'll mine a block which should include the sweep transaction we // We'll mine a block which should include the sweep transaction we
// generated above. // generated above.
@@ -849,6 +839,7 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
for _, txn := range txResp.Transactions { for _, txn := range txResp.Transactions {
if txn.TxHash == targetTx { if txn.TxHash == targetTx {
target = txn target = txn
break
} }
} }
@@ -883,28 +874,29 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
// label our transaction with an empty label, and check that we fail as // label our transaction with an empty label, and check that we fail as
// expected. // expected.
sweepHash := sweepTx.TxHash() sweepHash := sweepTx.TxHash()
req := &walletrpc.LabelTransactionRequest{ err := ainz.RPC.LabelTransactionAssertErr(
Txid: sweepHash[:], &walletrpc.LabelTransactionRequest{
Label: "", Txid: sweepHash[:],
Overwrite: false, Label: "",
} Overwrite: false,
err := ainz.RPC.LabelTransactionAssertErr(req) },
)
// Our error will be wrapped in a rpc error, so we check that it // Our error will be wrapped in a rpc error, so we check that it
// contains the error we expect. // contains the error we expect.
errZeroLabel := "cannot label transaction with empty label" errZeroLabel := "cannot label transaction with empty label"
require.Contains(ht, err.Error(), errZeroLabel, require.Contains(ht, err.Error(), errZeroLabel)
"expected: zero label errorv")
// Next, we try to relabel our transaction without setting the overwrite // Next, we try to relabel our transaction without setting the overwrite
// boolean. We expect this to fail, because the wallet requires setting // boolean. We expect this to fail, because the wallet requires setting
// of this param to prevent accidental overwrite of labels. // of this param to prevent accidental overwrite of labels.
req = &walletrpc.LabelTransactionRequest{ err = ainz.RPC.LabelTransactionAssertErr(
Txid: sweepHash[:], &walletrpc.LabelTransactionRequest{
Label: "label that will not work", Txid: sweepHash[:],
Overwrite: false, Label: "label that will not work",
} Overwrite: false,
err = ainz.RPC.LabelTransactionAssertErr(req) },
)
// Our error will be wrapped in a rpc error, so we check that it // Our error will be wrapped in a rpc error, so we check that it
// contains the error we expect. // contains the error we expect.
@@ -913,12 +905,11 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
// Finally, we overwrite our label with a new label, which should not // Finally, we overwrite our label with a new label, which should not
// fail. // fail.
newLabel := "new sweep tx label" newLabel := "new sweep tx label"
req = &walletrpc.LabelTransactionRequest{ ainz.RPC.LabelTransaction(&walletrpc.LabelTransactionRequest{
Txid: sweepHash[:], Txid: sweepHash[:],
Label: newLabel, Label: newLabel,
Overwrite: true, Overwrite: true,
} })
ainz.RPC.LabelTransaction(req)
waitTxLabel(sweepTxStr, newLabel) waitTxLabel(sweepTxStr, newLabel)
@@ -929,8 +920,60 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
// If we try again, but this time specifying an amount, then the call // If we try again, but this time specifying an amount, then the call
// should fail. // should fail.
sweepReq.Amount = 10000 ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
ainz.RPC.SendCoinsAssertErr(sweepReq) Addr: ht.Miner.NewMinerAddress().String(),
Amount: 10000,
SendAll: true,
Label: sendCoinsLabel,
})
// With all the edge cases tested, we'll now test the happy paths of
// change output types.
// We'll be using a "main" address where we send the funds to and from
// several times.
ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz)
mainAddrResp := ainz.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
})
spendAddrTypes := []lnrpc.AddressType{
lnrpc.AddressType_NESTED_PUBKEY_HASH,
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
lnrpc.AddressType_TAPROOT_PUBKEY,
}
for _, addrType := range spendAddrTypes {
ht.Logf("testing with address type %s", addrType)
// First, spend all the coins in the wallet to an address of the
// given type so that UTXO will be picked when sending coins.
sendAllCoinsToAddrType(ht, ainz, addrType)
// Let's send some coins to the main address.
const amt = 123456
resp := ainz.RPC.SendCoins(&lnrpc.SendCoinsRequest{
Addr: mainAddrResp.Address,
Amount: amt,
})
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
sweepTx := block.Transactions[1]
require.Equal(ht, sweepTx.TxHash().String(), resp.Txid)
// Find the change output, it will be the one with an amount
// different from the amount we sent.
var changeOutput *wire.TxOut
for idx := range sweepTx.TxOut {
txOut := sweepTx.TxOut[idx]
if txOut.Value != amt {
changeOutput = txOut
break
}
}
require.NotNil(ht, changeOutput)
// Assert the type of change output to be p2tr.
pkScript, err := txscript.ParsePkScript(changeOutput.PkScript)
require.NoError(ht, err)
require.Equal(ht, txscript.WitnessV1TaprootTy, pkScript.Class())
}
} }
// testListAddresses tests that we get all the addresses and their // testListAddresses tests that we get all the addresses and their
+9
View File
@@ -430,6 +430,15 @@ func testAnchorThirdPartySpend(ht *lntest.HarnessTest) {
commitTxn := ht.Miner.GetRawTransaction(forceCloseTxID) commitTxn := ht.Miner.GetRawTransaction(forceCloseTxID)
ht.Miner.MineBlockWithTxes([]*btcutil.Tx{commitTxn}) ht.Miner.MineBlockWithTxes([]*btcutil.Tx{commitTxn})
// Assert that the channel is now in PendingForceClose.
//
// NOTE: We must do this check to make sure `lnd` node has updated its
// internal state regarding the closing transaction, otherwise the
// `SendCoins` below might fail since it involves a reserved value
// check, which requires a certain amount of coins to be reserved based
// on the number of anchor channels.
ht.AssertChannelPendingForceClose(alice, aliceChanPoint1)
// With the anchor output located, and the main commitment mined we'll // With the anchor output located, and the main commitment mined we'll
// instruct the wallet to send all coins in the wallet to a new address // instruct the wallet to send all coins in the wallet to a new address
// (to the miner), including unconfirmed change. // (to the miner), including unconfirmed change.
+41 -2
View File
@@ -1009,17 +1009,28 @@ func assertPsbtSpend(ht *lntest.HarnessTest, alice *node.HarnessNode,
packet, err := psbt.NewFromUnsignedTx(pendingTx) packet, err := psbt.NewFromUnsignedTx(pendingTx)
require.NoError(ht, err) require.NoError(ht, err)
// We first try to sign the psbt without the necessary input data
// which should fail with the expected error.
var buf bytes.Buffer
err = packet.Serialize(&buf)
require.NoError(ht, err)
signReq := &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}
err = alice.RPC.SignPsbtErr(signReq)
require.ErrorContains(ht, err, "input (index=0) doesn't specify "+
"any UTXO info", "error does not match")
// Now let's add the meta information that we need for signing. // Now let's add the meta information that we need for signing.
packet.Inputs[0].WitnessUtxo = utxo packet.Inputs[0].WitnessUtxo = utxo
packet.Inputs[0].NonWitnessUtxo = prevTx packet.Inputs[0].NonWitnessUtxo = prevTx
decorateUnsigned(packet) decorateUnsigned(packet)
// That's it, we should be able to sign the PSBT now. // That's it, we should be able to sign the PSBT now.
var buf bytes.Buffer buf.Reset()
err = packet.Serialize(&buf) err = packet.Serialize(&buf)
require.NoError(ht, err) require.NoError(ht, err)
signReq := &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()} signReq = &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()}
signResp := alice.RPC.SignPsbt(signReq) signResp := alice.RPC.SignPsbt(signReq)
// Let's make sure we have a partial signature. // Let's make sure we have a partial signature.
@@ -1087,6 +1098,34 @@ func assertPsbtFundSignSpend(ht *lntest.HarnessTest, alice *node.HarnessNode,
) )
require.GreaterOrEqual(ht, fundResp.ChangeOutputIndex, int32(-1)) require.GreaterOrEqual(ht, fundResp.ChangeOutputIndex, int32(-1))
// Make sure our change output has all the meta information required for
// signing.
fundedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(fundResp.FundedPsbt), false,
)
require.NoError(ht, err)
pOut := fundedPacket.Outputs[fundResp.ChangeOutputIndex]
require.NotEmpty(ht, pOut.Bip32Derivation)
derivation := pOut.Bip32Derivation[0]
_, err = btcec.ParsePubKey(derivation.PubKey)
require.NoError(ht, err)
require.Len(ht, derivation.Bip32Path, 5)
// Ensure we get the change output properly decorated with all the new
// Taproot related fields, if it is a Taproot output.
if changeType == walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR {
require.NotEmpty(ht, pOut.TaprootBip32Derivation)
require.NotEmpty(ht, pOut.TaprootInternalKey)
trDerivation := pOut.TaprootBip32Derivation[0]
require.Equal(
ht, trDerivation.XOnlyPubKey, pOut.TaprootInternalKey,
)
_, err := schnorr.ParsePubKey(pOut.TaprootInternalKey)
require.NoError(ht, err)
}
var signedPsbt []byte var signedPsbt []byte
if useFinalize { if useFinalize {
finalizeResp := alice.RPC.FinalizePsbt( finalizeResp := alice.RPC.FinalizePsbt(
+172
View File
@@ -0,0 +1,172 @@
package itest
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testWatchtowerSessionManagement tests that session deletion is done
// correctly.
func testWatchtowerSessionManagement(ht *lntest.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10_000
numInvoices = 5
maxUpdates = numInvoices * 2
externalIP = "1.2.3.4"
sessionCloseRange = 1
)
// Set up Wallis the watchtower who will be used by Dave to watch over
// his channel commitment transactions.
wallis := ht.NewNode("Wallis", []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
})
wallisInfo := wallis.RPC.GetInfoWatchtower()
// Assert that Wallis has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, wallisInfo.Listeners, 1)
listener := wallisInfo.Listeners[0]
require.True(ht, listener == "0.0.0.0:9911" || listener == "[::]:9911")
// Assert the Wallis's URIs properly display the chosen external IP.
require.Len(ht, wallisInfo.Uris, 1)
require.Contains(ht, wallisInfo.Uris[0], externalIP)
// Dave will be the tower client.
daveArgs := []string{
"--wtclient.active",
fmt.Sprintf("--wtclient.max-updates=%d", maxUpdates),
fmt.Sprintf(
"--wtclient.session-close-range=%d", sessionCloseRange,
),
}
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: wallisInfo.Pubkey,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// Assert that there exists a session between Dave and Wallis.
err := wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey,
IncludeSessions: true,
})
var numSessions uint32
for _, sessionType := range info.SessionInfo {
numSessions += sessionType.NumSessions
}
if numSessions > 0 {
return nil
}
return fmt.Errorf("expected a non-zero number of sessions")
}, defaultTimeout)
require.NoError(ht, err)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Connect Dave and Alice.
ht.ConnectNodes(dave, ht.Alice)
// Open a channel between Dave and Alice.
params := lntest.OpenChannelParams{
Amt: chanAmt,
}
chanPoint := ht.OpenChannel(dave, ht.Alice, params)
// Since there are 2 updates made for every payment and the maximum
// number of updates per session has been set to 10, make 5 payments
// between the pair so that the session is exhausted.
alicePayReqs, _, _ := ht.CreatePayReqs(
ht.Alice, paymentAmt, numInvoices,
)
send := func(node *node.HarnessNode, payReq string) {
stream := node.RPC.SendPayment(&routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
})
ht.AssertPaymentStatusFromStream(
stream, lnrpc.Payment_SUCCEEDED,
)
}
for i := 0; i < numInvoices; i++ {
send(dave, alicePayReqs[i])
}
// assertNumBackups is a closure that asserts that Dave has a certain
// number of backups backed up to the tower. If mineOnFail is true,
// then a block will be mined each time the assertion fails.
assertNumBackups := func(expected int, mineOnFail bool) {
err = wait.NoError(func() error {
info := dave.RPC.GetTowerInfo(
&wtclientrpc.GetTowerInfoRequest{
Pubkey: wallisInfo.Pubkey,
IncludeSessions: true,
},
)
var numBackups uint32
for _, sessionType := range info.SessionInfo {
for _, session := range sessionType.Sessions {
numBackups += session.NumBackups
}
}
if numBackups == uint32(expected) {
return nil
}
if mineOnFail {
ht.Miner.MineBlocksSlow(1)
}
return fmt.Errorf("expected %d backups, got %d",
expected, numBackups)
}, defaultTimeout)
require.NoError(ht, err)
}
// Assert that one of the sessions now has 10 backups.
assertNumBackups(10, false)
// Now close the channel and wait for the close transaction to appear
// in the mempool so that it is included in a block when we mine.
ht.CloseChannelAssertPending(dave, chanPoint, false)
// Mine enough blocks to surpass the session-close-range. This should
// trigger the session to be deleted.
ht.MineBlocksAndAssertNumTxes(sessionCloseRange+6, 1)
// Wait for the session to be deleted. We know it has been deleted once
// the number of backups is back to zero. We check for number of backups
// instead of number of sessions because it is expected that the client
// would immediately negotiate another session after deleting the
// exhausted one. This time we set the "mineOnFail" parameter to true to
// ensure that the session deleting logic is run.
assertNumBackups(0, true)
}
+9
View File
@@ -17,6 +17,15 @@ type WtClient struct {
// SweepFeeRate specifies the fee rate in sat/byte to be used when // SweepFeeRate specifies the fee rate in sat/byte to be used when
// constructing justice transactions sent to the tower. // constructing justice transactions sent to the tower.
SweepFeeRate uint64 `long:"sweep-fee-rate" description:"Specifies the fee rate in sat/byte to be used when constructing justice transactions sent to the watchtower."` SweepFeeRate uint64 `long:"sweep-fee-rate" description:"Specifies the fee rate in sat/byte to be used when constructing justice transactions sent to the watchtower."`
// SessionCloseRange is the range over which to choose a random number
// of blocks to wait after the last channel of a session is closed
// before sending the DeleteSession message to the tower server.
SessionCloseRange uint32 `long:"session-close-range" description:"The range over which to choose a random number of blocks to wait after the last channel of a session is closed before sending the DeleteSession message to the tower server. Set to 1 for no delay."`
// MaxUpdates is the maximum number of updates to be backed up in a
// single tower sessions.
MaxUpdates uint16 `long:"max-updates" description:"The maximum number of updates to be backed up in a single session."`
} }
// Validate ensures the user has provided a valid configuration. // Validate ensures the user has provided a valid configuration.
+1 -1
View File
@@ -348,7 +348,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
// We'll use our current default CLTV value unless one was specified as // We'll use our current default CLTV value unless one was specified as
// an option on the command line when creating an invoice. // an option on the command line when creating an invoice.
switch { switch {
case invoice.CltvExpiry > math.MaxUint16: case invoice.CltvExpiry > routing.MaxCLTVDelta:
return nil, nil, fmt.Errorf("CLTV delta of %v is too large, "+ return nil, nil, fmt.Errorf("CLTV delta of %v is too large, "+
"max accepted is: %v", invoice.CltvExpiry, "max accepted is: %v", invoice.CltvExpiry,
math.MaxUint16) math.MaxUint16)
+2359 -2347
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -1542,6 +1542,9 @@ message Channel {
// The configured alias name of our peer. // The configured alias name of our peer.
string peer_alias = 34; string peer_alias = 34;
// This is the peer SCID alias.
uint64 peer_scid_alias = 35 [jstype = JS_STRING];
} }
message ListChannelsRequest { message ListChannelsRequest {
+5
View File
@@ -3568,6 +3568,11 @@
"peer_alias": { "peer_alias": {
"type": "string", "type": "string",
"description": "The configured alias name of our peer." "description": "The configured alias name of our peer."
},
"peer_scid_alias": {
"type": "string",
"format": "uint64",
"description": "This is the peer SCID alias."
} }
} }
}, },
+107 -32
View File
@@ -1,10 +1,13 @@
package wtclientrpc package wtclientrpc
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sort"
"strconv" "strconv"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
@@ -266,7 +269,7 @@ func (c *WatchtowerClient) ListTowers(ctx context.Context,
} }
opts, ackCounts, committedUpdateCounts := constructFunctionalOptions( opts, ackCounts, committedUpdateCounts := constructFunctionalOptions(
req.IncludeSessions, req.IncludeSessions, req.ExcludeExhaustedSessions,
) )
anchorTowers, err := c.cfg.AnchorClient.RegisteredTowers(opts...) anchorTowers, err := c.cfg.AnchorClient.RegisteredTowers(opts...)
@@ -274,30 +277,47 @@ func (c *WatchtowerClient) ListTowers(ctx context.Context,
return nil, err return nil, err
} }
// Collect all the anchor client towers.
rpcTowers := make(map[wtdb.TowerID]*Tower)
for _, tower := range anchorTowers {
rpcTower := marshallTower(
tower, PolicyType_ANCHOR, req.IncludeSessions,
ackCounts, committedUpdateCounts,
)
rpcTowers[tower.ID] = rpcTower
}
legacyTowers, err := c.cfg.Client.RegisteredTowers(opts...) legacyTowers, err := c.cfg.Client.RegisteredTowers(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Filter duplicates. // Collect all the legacy client towers. If it has any of the same
towers := make(map[wtdb.TowerID]*wtclient.RegisteredTower) // towers that the anchors client has, then just add the session info
for _, tower := range anchorTowers { // for the legacy client to the existing tower.
towers[tower.Tower.ID] = tower
}
for _, tower := range legacyTowers { for _, tower := range legacyTowers {
towers[tower.Tower.ID] = tower
}
rpcTowers := make([]*Tower, 0, len(towers))
for _, tower := range towers {
rpcTower := marshallTower( rpcTower := marshallTower(
tower, req.IncludeSessions, ackCounts, tower, PolicyType_LEGACY, req.IncludeSessions,
committedUpdateCounts, ackCounts, committedUpdateCounts,
) )
rpcTowers = append(rpcTowers, rpcTower)
t, ok := rpcTowers[tower.ID]
if !ok {
rpcTowers[tower.ID] = rpcTower
continue
}
t.SessionInfo = append(t.SessionInfo, rpcTower.SessionInfo...)
t.Sessions = append(t.Sessions, rpcTower.Sessions...)
} }
return &ListTowersResponse{Towers: rpcTowers}, nil towers := make([]*Tower, 0, len(rpcTowers))
for _, tower := range rpcTowers {
towers = append(towers, tower)
}
return &ListTowersResponse{Towers: towers}, nil
} }
// GetTowerInfo retrieves information for a registered watchtower. // GetTowerInfo retrieves information for a registered watchtower.
@@ -314,30 +334,52 @@ func (c *WatchtowerClient) GetTowerInfo(ctx context.Context,
} }
opts, ackCounts, committedUpdateCounts := constructFunctionalOptions( opts, ackCounts, committedUpdateCounts := constructFunctionalOptions(
req.IncludeSessions, req.IncludeSessions, req.ExcludeExhaustedSessions,
) )
var tower *wtclient.RegisteredTower // Get the tower and its sessions from anchors client.
tower, err = c.cfg.Client.LookupTower(pubKey, opts...) tower, err := c.cfg.AnchorClient.LookupTower(pubKey, opts...)
if err == wtdb.ErrTowerNotFound { if err != nil {
tower, err = c.cfg.AnchorClient.LookupTower(pubKey, opts...) return nil, err
} }
rpcTower := marshallTower(
tower, PolicyType_ANCHOR, req.IncludeSessions, ackCounts,
committedUpdateCounts,
)
// Get the tower and its sessions from legacy client.
tower, err = c.cfg.Client.LookupTower(pubKey, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return marshallTower( rpcLegacyTower := marshallTower(
tower, req.IncludeSessions, ackCounts, committedUpdateCounts, tower, PolicyType_LEGACY, req.IncludeSessions, ackCounts,
), nil committedUpdateCounts,
)
if !bytes.Equal(rpcTower.Pubkey, rpcLegacyTower.Pubkey) {
return nil, fmt.Errorf("legacy and anchor clients returned " +
"inconsistent results for the given tower")
}
rpcTower.SessionInfo = append(
rpcTower.SessionInfo, rpcLegacyTower.SessionInfo...,
)
rpcTower.Sessions = append(
rpcTower.Sessions, rpcLegacyTower.Sessions...,
)
return rpcTower, nil
} }
// constructFunctionalOptions is a helper function that constructs a list of // constructFunctionalOptions is a helper function that constructs a list of
// functional options to be used when fetching a tower from the DB. It also // functional options to be used when fetching a tower from the DB. It also
// returns a map of acked-update counts and one for un-acked-update counts that // returns a map of acked-update counts and one for un-acked-update counts that
// will be populated once the db call has been made. // will be populated once the db call has been made.
func constructFunctionalOptions(includeSessions bool) ( func constructFunctionalOptions(includeSessions,
[]wtdb.ClientSessionListOption, map[wtdb.SessionID]uint16, excludeExhaustedSessions bool) ([]wtdb.ClientSessionListOption,
map[wtdb.SessionID]uint16) { map[wtdb.SessionID]uint16, map[wtdb.SessionID]uint16) {
var ( var (
opts []wtdb.ClientSessionListOption opts []wtdb.ClientSessionListOption
@@ -365,6 +407,12 @@ func constructFunctionalOptions(includeSessions bool) (
wtdb.WithPerCommittedUpdate(perCommittedUpdate), wtdb.WithPerCommittedUpdate(perCommittedUpdate),
} }
if excludeExhaustedSessions {
opts = append(opts, wtdb.WithPostEvalFilterFn(
wtclient.ExhaustedSessionFilter(),
))
}
return opts, ackCounts, committedUpdateCounts return opts, ackCounts, committedUpdateCounts
} }
@@ -437,8 +485,8 @@ func (c *WatchtowerClient) Policy(ctx context.Context,
// marshallTower converts a client registered watchtower into its corresponding // marshallTower converts a client registered watchtower into its corresponding
// RPC type. // RPC type.
func marshallTower(tower *wtclient.RegisteredTower, includeSessions bool, func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType,
ackCounts map[wtdb.SessionID]uint16, includeSessions bool, ackCounts map[wtdb.SessionID]uint16,
pendingCounts map[wtdb.SessionID]uint16) *Tower { pendingCounts map[wtdb.SessionID]uint16) *Tower {
rpcAddrs := make([]string, 0, len(tower.Addresses)) rpcAddrs := make([]string, 0, len(tower.Addresses))
@@ -448,8 +496,24 @@ func marshallTower(tower *wtclient.RegisteredTower, includeSessions bool,
var rpcSessions []*TowerSession var rpcSessions []*TowerSession
if includeSessions { if includeSessions {
rpcSessions = make([]*TowerSession, 0, len(tower.Sessions)) // To ensure that the output order is deterministic for a given
// set of sessions, we put the sessions into a slice and order
// them based on session ID.
sessions := make([]*wtdb.ClientSession, 0, len(tower.Sessions))
for _, session := range tower.Sessions { for _, session := range tower.Sessions {
sessions = append(sessions, session)
}
sort.Slice(sessions, func(i, j int) bool {
id1 := sessions[i].ID
id2 := sessions[j].ID
return binary.BigEndian.Uint64(id1[:]) <
binary.BigEndian.Uint64(id2[:])
})
rpcSessions = make([]*TowerSession, 0, len(tower.Sessions))
for _, session := range sessions {
satPerVByte := session.Policy.SweepFeeRate.FeePerKVByte() / 1000 satPerVByte := session.Policy.SweepFeeRate.FeePerKVByte() / 1000
rpcSessions = append(rpcSessions, &TowerSession{ rpcSessions = append(rpcSessions, &TowerSession{
NumBackups: uint32(ackCounts[session.ID]), NumBackups: uint32(ackCounts[session.ID]),
@@ -463,11 +527,22 @@ func marshallTower(tower *wtclient.RegisteredTower, includeSessions bool,
} }
} }
return &Tower{ rpcTower := &Tower{
Pubkey: tower.IdentityKey.SerializeCompressed(), Pubkey: tower.IdentityKey.SerializeCompressed(),
Addresses: rpcAddrs, Addresses: rpcAddrs,
SessionInfo: []*TowerSessionInfo{{
PolicyType: policyType,
ActiveSessionCandidate: tower.ActiveSessionCandidate,
NumSessions: uint32(len(tower.Sessions)),
Sessions: rpcSessions,
}},
// The below fields are populated for backwards compatibility
// but will be removed in a future commit when the proto fields
// are removed.
ActiveSessionCandidate: tower.ActiveSessionCandidate, ActiveSessionCandidate: tower.ActiveSessionCandidate,
NumSessions: uint32(len(tower.Sessions)), NumSessions: uint32(len(tower.Sessions)),
Sessions: rpcSessions, Sessions: rpcSessions,
} }
return rpcTower
} }
+316 -154
View File
@@ -269,6 +269,9 @@ type GetTowerInfoRequest struct {
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// Whether we should include sessions with the watchtower in the response. // Whether we should include sessions with the watchtower in the response.
IncludeSessions bool `protobuf:"varint,2,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"` IncludeSessions bool `protobuf:"varint,2,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"`
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
ExcludeExhaustedSessions bool `protobuf:"varint,3,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"`
} }
func (x *GetTowerInfoRequest) Reset() { func (x *GetTowerInfoRequest) Reset() {
@@ -317,6 +320,13 @@ func (x *GetTowerInfoRequest) GetIncludeSessions() bool {
return false return false
} }
func (x *GetTowerInfoRequest) GetExcludeExhaustedSessions() bool {
if x != nil {
return x.ExcludeExhaustedSessions
}
return false
}
type TowerSession struct { type TowerSession struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -418,12 +428,26 @@ type Tower struct {
Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
// The list of addresses the watchtower is reachable over. // The list of addresses the watchtower is reachable over.
Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"`
// Deprecated, use the active_session_candidate field under the
// correct identifier in the client_type map.
// Whether the watchtower is currently a candidate for new sessions. // Whether the watchtower is currently a candidate for new sessions.
//
// Deprecated: Do not use.
ActiveSessionCandidate bool `protobuf:"varint,3,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"` ActiveSessionCandidate bool `protobuf:"varint,3,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"`
// Deprecated, use the num_sessions field under the correct identifier
// in the client_type map.
// The number of sessions that have been negotiated with the watchtower. // The number of sessions that have been negotiated with the watchtower.
//
// Deprecated: Do not use.
NumSessions uint32 `protobuf:"varint,4,opt,name=num_sessions,json=numSessions,proto3" json:"num_sessions,omitempty"` NumSessions uint32 `protobuf:"varint,4,opt,name=num_sessions,json=numSessions,proto3" json:"num_sessions,omitempty"`
// Deprecated, use the sessions field under the correct identifier in the
// client_type map.
// The list of sessions that have been negotiated with the watchtower. // The list of sessions that have been negotiated with the watchtower.
//
// Deprecated: Do not use.
Sessions []*TowerSession `protobuf:"bytes,5,rep,name=sessions,proto3" json:"sessions,omitempty"` Sessions []*TowerSession `protobuf:"bytes,5,rep,name=sessions,proto3" json:"sessions,omitempty"`
// A list sessions held with the tower.
SessionInfo []*TowerSessionInfo `protobuf:"bytes,6,rep,name=session_info,json=sessionInfo,proto3" json:"session_info,omitempty"`
} }
func (x *Tower) Reset() { func (x *Tower) Reset() {
@@ -472,6 +496,7 @@ func (x *Tower) GetAddresses() []string {
return nil return nil
} }
// Deprecated: Do not use.
func (x *Tower) GetActiveSessionCandidate() bool { func (x *Tower) GetActiveSessionCandidate() bool {
if x != nil { if x != nil {
return x.ActiveSessionCandidate return x.ActiveSessionCandidate
@@ -479,6 +504,7 @@ func (x *Tower) GetActiveSessionCandidate() bool {
return false return false
} }
// Deprecated: Do not use.
func (x *Tower) GetNumSessions() uint32 { func (x *Tower) GetNumSessions() uint32 {
if x != nil { if x != nil {
return x.NumSessions return x.NumSessions
@@ -486,6 +512,7 @@ func (x *Tower) GetNumSessions() uint32 {
return 0 return 0
} }
// Deprecated: Do not use.
func (x *Tower) GetSessions() []*TowerSession { func (x *Tower) GetSessions() []*TowerSession {
if x != nil { if x != nil {
return x.Sessions return x.Sessions
@@ -493,6 +520,88 @@ func (x *Tower) GetSessions() []*TowerSession {
return nil return nil
} }
func (x *Tower) GetSessionInfo() []*TowerSessionInfo {
if x != nil {
return x.SessionInfo
}
return nil
}
type TowerSessionInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Whether the watchtower is currently a candidate for new sessions.
ActiveSessionCandidate bool `protobuf:"varint,1,opt,name=active_session_candidate,json=activeSessionCandidate,proto3" json:"active_session_candidate,omitempty"`
// The number of sessions that have been negotiated with the watchtower.
NumSessions uint32 `protobuf:"varint,2,opt,name=num_sessions,json=numSessions,proto3" json:"num_sessions,omitempty"`
// The list of sessions that have been negotiated with the watchtower.
Sessions []*TowerSession `protobuf:"bytes,3,rep,name=sessions,proto3" json:"sessions,omitempty"`
// The session's policy type.
PolicyType PolicyType `protobuf:"varint,4,opt,name=policy_type,json=policyType,proto3,enum=wtclientrpc.PolicyType" json:"policy_type,omitempty"`
}
func (x *TowerSessionInfo) Reset() {
*x = TowerSessionInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TowerSessionInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TowerSessionInfo) ProtoMessage() {}
func (x *TowerSessionInfo) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7]
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 TowerSessionInfo.ProtoReflect.Descriptor instead.
func (*TowerSessionInfo) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{7}
}
func (x *TowerSessionInfo) GetActiveSessionCandidate() bool {
if x != nil {
return x.ActiveSessionCandidate
}
return false
}
func (x *TowerSessionInfo) GetNumSessions() uint32 {
if x != nil {
return x.NumSessions
}
return 0
}
func (x *TowerSessionInfo) GetSessions() []*TowerSession {
if x != nil {
return x.Sessions
}
return nil
}
func (x *TowerSessionInfo) GetPolicyType() PolicyType {
if x != nil {
return x.PolicyType
}
return PolicyType_LEGACY
}
type ListTowersRequest struct { type ListTowersRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -500,12 +609,15 @@ type ListTowersRequest struct {
// Whether we should include sessions with the watchtower in the response. // Whether we should include sessions with the watchtower in the response.
IncludeSessions bool `protobuf:"varint,1,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"` IncludeSessions bool `protobuf:"varint,1,opt,name=include_sessions,json=includeSessions,proto3" json:"include_sessions,omitempty"`
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
ExcludeExhaustedSessions bool `protobuf:"varint,2,opt,name=exclude_exhausted_sessions,json=excludeExhaustedSessions,proto3" json:"exclude_exhausted_sessions,omitempty"`
} }
func (x *ListTowersRequest) Reset() { func (x *ListTowersRequest) Reset() {
*x = ListTowersRequest{} *x = ListTowersRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -518,7 +630,7 @@ func (x *ListTowersRequest) String() string {
func (*ListTowersRequest) ProtoMessage() {} func (*ListTowersRequest) ProtoMessage() {}
func (x *ListTowersRequest) ProtoReflect() protoreflect.Message { func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[7] mi := &file_wtclientrpc_wtclient_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -531,7 +643,7 @@ func (x *ListTowersRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListTowersRequest.ProtoReflect.Descriptor instead.
func (*ListTowersRequest) Descriptor() ([]byte, []int) { func (*ListTowersRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{7} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8}
} }
func (x *ListTowersRequest) GetIncludeSessions() bool { func (x *ListTowersRequest) GetIncludeSessions() bool {
@@ -541,6 +653,13 @@ func (x *ListTowersRequest) GetIncludeSessions() bool {
return false return false
} }
func (x *ListTowersRequest) GetExcludeExhaustedSessions() bool {
if x != nil {
return x.ExcludeExhaustedSessions
}
return false
}
type ListTowersResponse struct { type ListTowersResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -553,7 +672,7 @@ type ListTowersResponse struct {
func (x *ListTowersResponse) Reset() { func (x *ListTowersResponse) Reset() {
*x = ListTowersResponse{} *x = ListTowersResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -566,7 +685,7 @@ func (x *ListTowersResponse) String() string {
func (*ListTowersResponse) ProtoMessage() {} func (*ListTowersResponse) ProtoMessage() {}
func (x *ListTowersResponse) ProtoReflect() protoreflect.Message { func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[8] mi := &file_wtclientrpc_wtclient_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -579,7 +698,7 @@ func (x *ListTowersResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListTowersResponse.ProtoReflect.Descriptor instead.
func (*ListTowersResponse) Descriptor() ([]byte, []int) { func (*ListTowersResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{8} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9}
} }
func (x *ListTowersResponse) GetTowers() []*Tower { func (x *ListTowersResponse) GetTowers() []*Tower {
@@ -598,7 +717,7 @@ type StatsRequest struct {
func (x *StatsRequest) Reset() { func (x *StatsRequest) Reset() {
*x = StatsRequest{} *x = StatsRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -611,7 +730,7 @@ func (x *StatsRequest) String() string {
func (*StatsRequest) ProtoMessage() {} func (*StatsRequest) ProtoMessage() {}
func (x *StatsRequest) ProtoReflect() protoreflect.Message { func (x *StatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[9] mi := &file_wtclientrpc_wtclient_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -624,7 +743,7 @@ func (x *StatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use StatsRequest.ProtoReflect.Descriptor instead.
func (*StatsRequest) Descriptor() ([]byte, []int) { func (*StatsRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{9} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10}
} }
type StatsResponse struct { type StatsResponse struct {
@@ -650,7 +769,7 @@ type StatsResponse struct {
func (x *StatsResponse) Reset() { func (x *StatsResponse) Reset() {
*x = StatsResponse{} *x = StatsResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -663,7 +782,7 @@ func (x *StatsResponse) String() string {
func (*StatsResponse) ProtoMessage() {} func (*StatsResponse) ProtoMessage() {}
func (x *StatsResponse) ProtoReflect() protoreflect.Message { func (x *StatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[10] mi := &file_wtclientrpc_wtclient_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -676,7 +795,7 @@ func (x *StatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use StatsResponse.ProtoReflect.Descriptor instead.
func (*StatsResponse) Descriptor() ([]byte, []int) { func (*StatsResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{10} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11}
} }
func (x *StatsResponse) GetNumBackups() uint32 { func (x *StatsResponse) GetNumBackups() uint32 {
@@ -726,7 +845,7 @@ type PolicyRequest struct {
func (x *PolicyRequest) Reset() { func (x *PolicyRequest) Reset() {
*x = PolicyRequest{} *x = PolicyRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -739,7 +858,7 @@ func (x *PolicyRequest) String() string {
func (*PolicyRequest) ProtoMessage() {} func (*PolicyRequest) ProtoMessage() {}
func (x *PolicyRequest) ProtoReflect() protoreflect.Message { func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[11] mi := &file_wtclientrpc_wtclient_proto_msgTypes[12]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -752,7 +871,7 @@ func (x *PolicyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead. // Deprecated: Use PolicyRequest.ProtoReflect.Descriptor instead.
func (*PolicyRequest) Descriptor() ([]byte, []int) { func (*PolicyRequest) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{11} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12}
} }
func (x *PolicyRequest) GetPolicyType() PolicyType { func (x *PolicyRequest) GetPolicyType() PolicyType {
@@ -784,7 +903,7 @@ type PolicyResponse struct {
func (x *PolicyResponse) Reset() { func (x *PolicyResponse) Reset() {
*x = PolicyResponse{} *x = PolicyResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -797,7 +916,7 @@ func (x *PolicyResponse) String() string {
func (*PolicyResponse) ProtoMessage() {} func (*PolicyResponse) ProtoMessage() {}
func (x *PolicyResponse) ProtoReflect() protoreflect.Message { func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_wtclientrpc_wtclient_proto_msgTypes[12] mi := &file_wtclientrpc_wtclient_proto_msgTypes[13]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -810,7 +929,7 @@ func (x *PolicyResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead. // Deprecated: Use PolicyResponse.ProtoReflect.Descriptor instead.
func (*PolicyResponse) Descriptor() ([]byte, []int) { func (*PolicyResponse) Descriptor() ([]byte, []int) {
return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{12} return file_wtclientrpc_wtclient_proto_rawDescGZIP(), []int{13}
} }
func (x *PolicyResponse) GetMaxUpdates() uint32 { func (x *PolicyResponse) GetMaxUpdates() uint32 {
@@ -852,113 +971,140 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{
0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x58, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e,
0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65,
0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63,
0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65,
0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74,
0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 0x54,
0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6e,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13,
0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2f, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b,
0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65,
0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2f, 0x0a,
0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62,
0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0xd1, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73,
0x01, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f,
0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65,
0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x38, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x22, 0x9f, 0x02,
0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x05, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65,
0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12,
0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x3c, 0x0a,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x42,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x02, 0x18, 0x01, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0c, 0x6e,
0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
0x6e, 0x73, 0x22, 0x3e, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72,
0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42,
0x02, 0x18, 0x01, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x40, 0x0a,
0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
0x66, 0x6f, 0x52, 0x0b, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x22,
0xe0, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x38, 0x0a, 0x18, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73,
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21,
0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x12, 0x35, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08,
0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79,
0x70, 0x65, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75,
0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x64, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x73, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x78,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77, 0x65, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x45,
0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74, 0x6f, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x77, 0x65, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x22, 0x40, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65,
0x75, 0x65, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x73,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x06, 0x74, 0x6f, 0x77, 0x65,
0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x72, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x73, 0x74, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b,
0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6e, 0x75, 0x6d, 0x42, 0x61,
0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x65, 0x6e,
0x01, 0x28, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01,
0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x28, 0x0d, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x04, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x5f, 0x66, 0x61, 0x69,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x6c, 0x65, 0x64, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
0x73, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x0d, 0x52, 0x10, 0x6e, 0x75, 0x6d, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b,
0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x75, 0x70, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x6e, 0x75, 0x6d, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69,
0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x6f, 0x6e, 0x73, 0x5f, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x28, 0x0d, 0x52, 0x13, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x41,
0x49, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x5f, 0x73,
0x12, 0x38, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65,
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x53, 0x65, 0x73, 0x73,
0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x78, 0x68, 0x61, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x49, 0x0a,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x50, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20,
0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x70, 0x6f,
0x0a, 0x12, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c,
0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d,
0x73, 0x77, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
0x2d, 0x0a, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12,
0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x79,
0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x24, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x73, 0x77,
0x0a, 0x0a, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a,
0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76,
0x4f, 0x52, 0x10, 0x01, 0x32, 0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65, 0x65,
0x77, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x24, 0x0a, 0x0a,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45,
0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52,
0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x10, 0x01, 0x32, 0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65,
0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f,
0x73, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x72, 0x12, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
0x72, 0x73, 0x12, 0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73,
0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x12, 0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x6e, 0x66, 0x6f, 0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x1a, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x65, 0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66,
0x74, 0x73, 0x12, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x6f, 0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73,
0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x12, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74,
0x1b, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x79, 0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 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, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@@ -974,7 +1120,7 @@ func file_wtclientrpc_wtclient_proto_rawDescGZIP() []byte {
} }
var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_wtclientrpc_wtclient_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_wtclientrpc_wtclient_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{ var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{
(PolicyType)(0), // 0: wtclientrpc.PolicyType (PolicyType)(0), // 0: wtclientrpc.PolicyType
(*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest (*AddTowerRequest)(nil), // 1: wtclientrpc.AddTowerRequest
@@ -984,34 +1130,38 @@ var file_wtclientrpc_wtclient_proto_goTypes = []interface{}{
(*GetTowerInfoRequest)(nil), // 5: wtclientrpc.GetTowerInfoRequest (*GetTowerInfoRequest)(nil), // 5: wtclientrpc.GetTowerInfoRequest
(*TowerSession)(nil), // 6: wtclientrpc.TowerSession (*TowerSession)(nil), // 6: wtclientrpc.TowerSession
(*Tower)(nil), // 7: wtclientrpc.Tower (*Tower)(nil), // 7: wtclientrpc.Tower
(*ListTowersRequest)(nil), // 8: wtclientrpc.ListTowersRequest (*TowerSessionInfo)(nil), // 8: wtclientrpc.TowerSessionInfo
(*ListTowersResponse)(nil), // 9: wtclientrpc.ListTowersResponse (*ListTowersRequest)(nil), // 9: wtclientrpc.ListTowersRequest
(*StatsRequest)(nil), // 10: wtclientrpc.StatsRequest (*ListTowersResponse)(nil), // 10: wtclientrpc.ListTowersResponse
(*StatsResponse)(nil), // 11: wtclientrpc.StatsResponse (*StatsRequest)(nil), // 11: wtclientrpc.StatsRequest
(*PolicyRequest)(nil), // 12: wtclientrpc.PolicyRequest (*StatsResponse)(nil), // 12: wtclientrpc.StatsResponse
(*PolicyResponse)(nil), // 13: wtclientrpc.PolicyResponse (*PolicyRequest)(nil), // 13: wtclientrpc.PolicyRequest
(*PolicyResponse)(nil), // 14: wtclientrpc.PolicyResponse
} }
var file_wtclientrpc_wtclient_proto_depIdxs = []int32{ var file_wtclientrpc_wtclient_proto_depIdxs = []int32{
6, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession 6, // 0: wtclientrpc.Tower.sessions:type_name -> wtclientrpc.TowerSession
7, // 1: wtclientrpc.ListTowersResponse.towers:type_name -> wtclientrpc.Tower 8, // 1: wtclientrpc.Tower.session_info:type_name -> wtclientrpc.TowerSessionInfo
0, // 2: wtclientrpc.PolicyRequest.policy_type:type_name -> wtclientrpc.PolicyType 6, // 2: wtclientrpc.TowerSessionInfo.sessions:type_name -> wtclientrpc.TowerSession
1, // 3: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest 0, // 3: wtclientrpc.TowerSessionInfo.policy_type:type_name -> wtclientrpc.PolicyType
3, // 4: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest 7, // 4: wtclientrpc.ListTowersResponse.towers:type_name -> wtclientrpc.Tower
8, // 5: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest 0, // 5: wtclientrpc.PolicyRequest.policy_type:type_name -> wtclientrpc.PolicyType
5, // 6: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest 1, // 6: wtclientrpc.WatchtowerClient.AddTower:input_type -> wtclientrpc.AddTowerRequest
10, // 7: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest 3, // 7: wtclientrpc.WatchtowerClient.RemoveTower:input_type -> wtclientrpc.RemoveTowerRequest
12, // 8: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest 9, // 8: wtclientrpc.WatchtowerClient.ListTowers:input_type -> wtclientrpc.ListTowersRequest
2, // 9: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse 5, // 9: wtclientrpc.WatchtowerClient.GetTowerInfo:input_type -> wtclientrpc.GetTowerInfoRequest
4, // 10: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse 11, // 10: wtclientrpc.WatchtowerClient.Stats:input_type -> wtclientrpc.StatsRequest
9, // 11: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse 13, // 11: wtclientrpc.WatchtowerClient.Policy:input_type -> wtclientrpc.PolicyRequest
7, // 12: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower 2, // 12: wtclientrpc.WatchtowerClient.AddTower:output_type -> wtclientrpc.AddTowerResponse
11, // 13: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse 4, // 13: wtclientrpc.WatchtowerClient.RemoveTower:output_type -> wtclientrpc.RemoveTowerResponse
13, // 14: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse 10, // 14: wtclientrpc.WatchtowerClient.ListTowers:output_type -> wtclientrpc.ListTowersResponse
9, // [9:15] is the sub-list for method output_type 7, // 15: wtclientrpc.WatchtowerClient.GetTowerInfo:output_type -> wtclientrpc.Tower
3, // [3:9] is the sub-list for method input_type 12, // 16: wtclientrpc.WatchtowerClient.Stats:output_type -> wtclientrpc.StatsResponse
3, // [3:3] is the sub-list for extension type_name 14, // 17: wtclientrpc.WatchtowerClient.Policy:output_type -> wtclientrpc.PolicyResponse
3, // [3:3] is the sub-list for extension extendee 12, // [12:18] is the sub-list for method output_type
0, // [0:3] is the sub-list for field type_name 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_wtclientrpc_wtclient_proto_init() } func init() { file_wtclientrpc_wtclient_proto_init() }
@@ -1105,7 +1255,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersRequest); i { switch v := v.(*TowerSessionInfo); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1117,7 +1267,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListTowersResponse); i { switch v := v.(*ListTowersRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1129,7 +1279,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsRequest); i { switch v := v.(*ListTowersResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1141,7 +1291,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StatsResponse); i { switch v := v.(*StatsRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1153,7 +1303,7 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyRequest); i { switch v := v.(*StatsResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -1165,6 +1315,18 @@ func file_wtclientrpc_wtclient_proto_init() {
} }
} }
file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { file_wtclientrpc_wtclient_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_wtclientrpc_wtclient_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PolicyResponse); i { switch v := v.(*PolicyResponse); i {
case 0: case 0:
return &v.state return &v.state
@@ -1183,7 +1345,7 @@ func file_wtclientrpc_wtclient_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc, RawDescriptor: file_wtclientrpc_wtclient_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 13, NumMessages: 14,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
+34 -3
View File
@@ -68,6 +68,10 @@ message GetTowerInfoRequest {
// Whether we should include sessions with the watchtower in the response. // Whether we should include sessions with the watchtower in the response.
bool include_sessions = 2; bool include_sessions = 2;
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
bool exclude_exhausted_sessions = 3;
} }
message TowerSession { message TowerSession {
@@ -107,19 +111,46 @@ message Tower {
// The list of addresses the watchtower is reachable over. // The list of addresses the watchtower is reachable over.
repeated string addresses = 2; repeated string addresses = 2;
// Deprecated, use the active_session_candidate field under the
// correct identifier in the client_type map.
// Whether the watchtower is currently a candidate for new sessions. // Whether the watchtower is currently a candidate for new sessions.
bool active_session_candidate = 3; bool active_session_candidate = 3 [deprecated = true];
// Deprecated, use the num_sessions field under the correct identifier
// in the client_type map.
// The number of sessions that have been negotiated with the watchtower.
uint32 num_sessions = 4 [deprecated = true];
// Deprecated, use the sessions field under the correct identifier in the
// client_type map.
// The list of sessions that have been negotiated with the watchtower.
repeated TowerSession sessions = 5 [deprecated = true];
// A list sessions held with the tower.
repeated TowerSessionInfo session_info = 6;
}
message TowerSessionInfo {
// Whether the watchtower is currently a candidate for new sessions.
bool active_session_candidate = 1;
// The number of sessions that have been negotiated with the watchtower. // The number of sessions that have been negotiated with the watchtower.
uint32 num_sessions = 4; uint32 num_sessions = 2;
// The list of sessions that have been negotiated with the watchtower. // The list of sessions that have been negotiated with the watchtower.
repeated TowerSession sessions = 5; repeated TowerSession sessions = 3;
// The session's policy type.
PolicyType policy_type = 4;
} }
message ListTowersRequest { message ListTowersRequest {
// Whether we should include sessions with the watchtower in the response. // Whether we should include sessions with the watchtower in the response.
bool include_sessions = 1; bool include_sessions = 1;
// Whether to exclude exhausted sessions in the response info. This option
// is only meaningful if include_sessions is true.
bool exclude_exhausted_sessions = 2;
} }
message ListTowersResponse { message ListTowersResponse {
+49 -3
View File
@@ -41,6 +41,13 @@
"in": "query", "in": "query",
"required": false, "required": false,
"type": "boolean" "type": "boolean"
},
{
"name": "exclude_exhausted_sessions",
"description": "Whether to exclude exhausted sessions in the response info. This option\nis only meaningful if include_sessions is true.",
"in": "query",
"required": false,
"type": "boolean"
} }
], ],
"tags": [ "tags": [
@@ -112,6 +119,13 @@
"in": "query", "in": "query",
"required": false, "required": false,
"type": "boolean" "type": "boolean"
},
{
"name": "exclude_exhausted_sessions",
"description": "Whether to exclude exhausted sessions in the response info. This option\nis only meaningful if include_sessions is true.",
"in": "query",
"required": false,
"type": "boolean"
} }
], ],
"tags": [ "tags": [
@@ -359,19 +373,26 @@
}, },
"active_session_candidate": { "active_session_candidate": {
"type": "boolean", "type": "boolean",
"description": "Whether the watchtower is currently a candidate for new sessions." "description": "Deprecated, use the active_session_candidate field under the\ncorrect identifier in the client_type map.\nWhether the watchtower is currently a candidate for new sessions."
}, },
"num_sessions": { "num_sessions": {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",
"description": "The number of sessions that have been negotiated with the watchtower." "description": "Deprecated, use the num_sessions field under the correct identifier\nin the client_type map.\nThe number of sessions that have been negotiated with the watchtower."
}, },
"sessions": { "sessions": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/wtclientrpcTowerSession" "$ref": "#/definitions/wtclientrpcTowerSession"
}, },
"description": "The list of sessions that have been negotiated with the watchtower." "description": "Deprecated, use the sessions field under the correct identifier in the\nclient_type map.\nThe list of sessions that have been negotiated with the watchtower."
},
"session_info": {
"type": "array",
"items": {
"$ref": "#/definitions/wtclientrpcTowerSessionInfo"
},
"description": "A list sessions held with the tower."
} }
} }
}, },
@@ -404,6 +425,31 @@
"description": "The fee rate, in satoshis per vbyte, that will be used by the watchtower for\nthe justice transaction in the event of a channel breach." "description": "The fee rate, in satoshis per vbyte, that will be used by the watchtower for\nthe justice transaction in the event of a channel breach."
} }
} }
},
"wtclientrpcTowerSessionInfo": {
"type": "object",
"properties": {
"active_session_candidate": {
"type": "boolean",
"description": "Whether the watchtower is currently a candidate for new sessions."
},
"num_sessions": {
"type": "integer",
"format": "int64",
"description": "The number of sessions that have been negotiated with the watchtower."
},
"sessions": {
"type": "array",
"items": {
"$ref": "#/definitions/wtclientrpcTowerSession"
},
"description": "The list of sessions that have been negotiated with the watchtower."
},
"policy_type": {
"$ref": "#/definitions/wtclientrpcPolicyType",
"description": "The session's policy type."
}
}
} }
} }
} }
+2 -2
View File
@@ -101,8 +101,8 @@ the real-world has. Aside from that, `lnd` has several places that use
different code, which is triggered by the build flag `integration`, to speed up different code, which is triggered by the build flag `integration`, to speed up
the tests. They are summarized as followings, the tests. They are summarized as followings,
1. `funding.checkPeerFundingLockInterval`, which is used when we wait for the 1. `funding.checkPeerChannelReadyInterval`, which is used when we wait for the
peer to send us `FundingLocked`. This value is 1 second in `lnd`, and 10 peer to send us `ChannelReady`. This value is 1 second in `lnd`, and 10
milliseconds in `lntest`. milliseconds in `lntest`.
2. `lncfg.ProtocolOptions`, which is used to specify protocol flags. In `lnd`, 2. `lncfg.ProtocolOptions`, which is used to specify protocol flags. In `lnd`,
anchor and script enforced lease are enabled by default, while in `lntest`, anchor and script enforced lease are enabled by default, while in `lntest`,
+41 -22
View File
@@ -299,9 +299,6 @@ func (h *HarnessTest) resetStandbyNodes(t *testing.T) {
// config for the coming test. This will also inherit the // config for the coming test. This will also inherit the
// test's running context. // test's running context.
h.RestartNodeWithExtraArgs(hn, hn.Cfg.OriginalExtraArgs) h.RestartNodeWithExtraArgs(hn, hn.Cfg.OriginalExtraArgs)
// Update the node's internal state.
hn.UpdateState()
} }
} }
@@ -433,8 +430,19 @@ func (h *HarnessTest) cleanupStandbyNode(hn *node.HarnessNode) {
// Delete all payments made from this test. // Delete all payments made from this test.
hn.RPC.DeleteAllPayments() hn.RPC.DeleteAllPayments()
// Finally, check the node is in a clean state for the following tests. // Check the node's current state with timeout.
h.validateNodeState(hn) //
// NOTE: we need to do this in a `wait` because it takes some time for
// the node to update its internal state. Once the RPCs are synced we
// can then remove this wait.
err := wait.NoError(func() error {
// Update the node's internal state.
hn.UpdateState()
// Check the node is in a clean state for the following tests.
return h.validateNodeState(hn)
}, wait.DefaultTimeout)
require.NoError(h, err, "timeout checking node's state")
} }
// removeConnectionns will remove all connections made on the standby nodes // removeConnectionns will remove all connections made on the standby nodes
@@ -752,32 +760,43 @@ func (h *HarnessTest) SetFeeEstimateWithConf(
// validateNodeState checks that the node doesn't have any uncleaned states // validateNodeState checks that the node doesn't have any uncleaned states
// which will affect its following tests. // which will affect its following tests.
func (h *HarnessTest) validateNodeState(hn *node.HarnessNode) { func (h *HarnessTest) validateNodeState(hn *node.HarnessNode) error {
errStr := func(subject string) string { errStr := func(subject string) error {
return fmt.Sprintf("%s: found %s channels, please close "+ return fmt.Errorf("%s: found %s channels, please close "+
"them properly", hn.Name(), subject) "them properly", hn.Name(), subject)
} }
// If the node still has open channels, it's most likely that the // If the node still has open channels, it's most likely that the
// current test didn't close it properly. // current test didn't close it properly.
require.Zerof(h, hn.State.OpenChannel.Active, errStr("active")) if hn.State.OpenChannel.Active != 0 {
require.Zerof(h, hn.State.OpenChannel.Public, errStr("public")) return errStr("active")
require.Zerof(h, hn.State.OpenChannel.Private, errStr("private")) }
require.Zerof(h, hn.State.OpenChannel.Pending, errStr("pending open")) if hn.State.OpenChannel.Public != 0 {
return errStr("public")
}
if hn.State.OpenChannel.Private != 0 {
return errStr("private")
}
if hn.State.OpenChannel.Pending != 0 {
return errStr("pending open")
}
// The number of pending force close channels should be zero. // The number of pending force close channels should be zero.
require.Zerof(h, hn.State.CloseChannel.PendingForceClose, if hn.State.CloseChannel.PendingForceClose != 0 {
errStr("pending force")) return errStr("pending force")
}
// The number of waiting close channels should be zero. // The number of waiting close channels should be zero.
require.Zerof(h, hn.State.CloseChannel.WaitingClose, if hn.State.CloseChannel.WaitingClose != 0 {
errStr("waiting close")) return errStr("waiting close")
}
// Ths number of payments should be zero. // Ths number of payments should be zero.
// TODO(yy): no need to check since it's deleted in the cleanup? Or if hn.State.Payment.Total != 0 {
// check it in a wait? return fmt.Errorf("%s: found uncleaned payments, please "+
require.Zerof(h, hn.State.Payment.Total, "%s: found "+ "delete all of them properly", hn.Name())
"uncleaned payments, please delete all of them properly", }
hn.Name())
return nil
} }
// GetChanPointFundingTxid takes a channel point and converts it into a chain // GetChanPointFundingTxid takes a channel point and converts it into a chain
@@ -1113,7 +1132,7 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode,
// active htlcs` or `link failed to shutdown` if we close the // active htlcs` or `link failed to shutdown` if we close the
// channel. We need to investigate the order of settling the // channel. We need to investigate the order of settling the
// payments and updating commitments to properly fix it. // payments and updating commitments to properly fix it.
time.Sleep(2 * time.Second) time.Sleep(5 * time.Second)
// Give it another chance. // Give it another chance.
stream = hn.RPC.CloseChannel(closeReq) stream = hn.RPC.CloseChannel(closeReq)
+13
View File
@@ -263,6 +263,19 @@ func (h *HarnessRPC) SignPsbt(
return resp return resp
} }
// SignPsbtErr makes a RPC call to the node's WalletKitClient and asserts
// an error returned.
func (h *HarnessRPC) SignPsbtErr(req *walletrpc.SignPsbtRequest) error {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
_, err := h.WalletKit.SignPsbt(ctxt, req)
require.Errorf(h, err, "%s: expect sign psbt to return an error",
h.Name)
return err
}
// ImportTapscript makes a RPC call to the node's WalletKitClient and asserts. // ImportTapscript makes a RPC call to the node's WalletKitClient and asserts.
// //
//nolint:lll //nolint:lll
+14
View File
@@ -24,6 +24,20 @@ func (h *HarnessRPC) GetInfoWatchtower() *watchtowerrpc.GetInfoResponse {
return info return info
} }
// GetTowerInfo makes an RPC call to the watchtower client of the given node and
// asserts.
func (h *HarnessRPC) GetTowerInfo(
req *wtclientrpc.GetTowerInfoRequest) *wtclientrpc.Tower {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
info, err := h.WatchtowerClient.GetTowerInfo(ctxt, req)
h.NoError(err, "GetTowerInfo from WatchtowerClient")
return info
}
// AddTower makes a RPC call to the WatchtowerClient of the given node and // AddTower makes a RPC call to the WatchtowerClient of the given node and
// asserts. // asserts.
func (h *HarnessRPC) AddTower( func (h *HarnessRPC) AddTower(
+3 -2
View File
@@ -135,8 +135,9 @@ func (b *BtcWallet) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
var signedInputs []uint32 var signedInputs []uint32
// Let's check that this is actually something we can and want to sign. // Let's check that this is actually something we can and want to sign.
// We need at least one input and one output. // We need at least one input and one output. In addition each
err := psbt.VerifyInputOutputLen(packet, true, true) // input needs nonWitness Utxo or witness Utxo data specified.
err := psbt.InputsReadyToSign(packet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+18 -18
View File
@@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"io" "io"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
@@ -204,6 +203,10 @@ func TestWebAPIFeeEstimator(t *testing.T) {
} }
// Construct mock fee source for the Estimator to pull fees from. // Construct mock fee source for the Estimator to pull fees from.
//
// This will create a `feeByBlockTarget` map with the following values,
// - 20: testFeeRate
// - 100: 42, which will be floored to feeFloor.
testFees := make(map[uint32]uint32) testFees := make(map[uint32]uint32)
for _, tc := range testCases { for _, tc := range testCases {
if tc.apiEst != 0 { if tc.apiEst != 0 {
@@ -233,25 +236,22 @@ func TestWebAPIFeeEstimator(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
est, err := estimator.EstimateFeePerKW(tc.target) est, err := estimator.EstimateFeePerKW(tc.target)
if tc.err != "" {
if err == nil ||
!strings.Contains(err.Error(), tc.err) {
t.Fatalf("expected fee estimation to "+ // Test an error case.
"fail, instead got: %v", err) if tc.err != "" {
} require.Error(t, err, "expected error")
} else { require.ErrorContains(t, err, tc.err)
exp := SatPerKVByte(tc.est).FeePerKWeight()
if err != nil { return
t.Fatalf("unable to estimate fee for "+
"%v block target, got: %v",
tc.target, err)
}
if est != exp {
t.Fatalf("expected fee estimate of "+
"%v, got %v", exp, est)
}
} }
// Test an non-error case.
require.NoErrorf(t, err, "error from target %v",
tc.target)
exp := SatPerKVByte(tc.est).FeePerKWeight()
require.Equalf(t, exp, est, "target %v failed, fee "+
"map is %v", tc.target, feeSource.fees)
}) })
} }
} }
+2 -2
View File
@@ -4063,7 +4063,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
// If we get a failure due to not knowing their next // If we get a failure due to not knowing their next
// point, then this is fine as they'll either send // point, then this is fine as they'll either send
// FundingLocked, or revoke their next state to allow // ChannelReady, or revoke their next state to allow
// us to continue forwards. // us to continue forwards.
case err == ErrNoWindow: case err == ErrNoWindow:
@@ -7220,7 +7220,7 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd
// Along with this revocation, we'll also send the _next_ commitment // Along with this revocation, we'll also send the _next_ commitment
// point that the remote party should use to create our next commitment // point that the remote party should use to create our next commitment
// transaction. We use a +2 here as we already gave them a look ahead // transaction. We use a +2 here as we already gave them a look ahead
// of size one after the FundingLocked message was sent: // of size one after the ChannelReady message was sent:
// //
// 0: current revocation, 1: their "next" revocation, 2: this revocation // 0: current revocation, 1: their "next" revocation, 2: this revocation
// //
+3 -2
View File
@@ -255,8 +255,9 @@ func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
// parameter in FinalizePsbt so we can get rid of this code duplication. // parameter in FinalizePsbt so we can get rid of this code duplication.
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error { func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
// Let's check that this is actually something we can and want to sign. // Let's check that this is actually something we can and want to sign.
// We need at least one input and one output. // We need at least one input and one output. In addition each
err := psbt.VerifyInputOutputLen(packet, true, true) // input needs nonWitness Utxo or witness Utxo data specified.
err := psbt.InputsReadyToSign(packet)
if err != nil { if err != nil {
return err return err
} }
+26 -3
View File
@@ -11,13 +11,13 @@
}, },
{ {
"Name": "simple commitment tx with no HTLCs and single anchor", "Name": "simple commitment tx with no HTLCs and single anchor",
"LocalBalance": 7000000000, "LocalBalance": 10000000000,
"RemoteBalance": 0, "RemoteBalance": 0,
"FeePerKw": 15000, "FeePerKw": 15000,
"UseTestHtlcs": false, "UseTestHtlcs": false,
"HtlcDescs": [], "HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f508b6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100da5310620e72bc23dc57af25d18102cc75479aea0258ab89fe1a66ca176033ec0220339efb450c12872e134c8bda986bb92f3e4eebcaa2d0fee5d9a2b1257d12f12a0147304402200dc30542c9b8b2ff4b8d98f46798b3218a088a07e97b9e786177287dc6a5347b02203d23b1c2bf17262362fdb4cdcc36dbb449a9efcdb10051ad52cfa09fc76842b001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", "ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f10529800000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022007cf6b405e9c9b4f527b0ecad9d8bb661fabb8b12abf7d1c0b3ad1855db3ed490220616d5c1eeadccc63bd775a131149455d62d95a42c2a1b01cc7821fc42dce7778014730440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a865801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "304402200dc30542c9b8b2ff4b8d98f46798b3218a088a07e97b9e786177287dc6a5347b02203d23b1c2bf17262362fdb4cdcc36dbb449a9efcdb10051ad52cfa09fc76842b0" "RemoteSigHex": "30440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a8658"
}, },
{ {
"Name": "commitment tx with seven outputs untrimmed (maximum feerate)", "Name": "commitment tx with seven outputs untrimmed (maximum feerate)",
@@ -237,5 +237,28 @@
"HtlcDescs": [], "HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a04004830450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1014830450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", "ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a04004830450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1014830450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf" "RemoteSigHex": "30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf"
},
{
"Name": "commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage",
"LocalBalance": 6987999999,
"RemoteBalance": 3000000000,
"FeePerKw": 253,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "30450221008c3060a17cb4799bc2290d692c8ae3d4a4ecbc7ed938d91709f7d3f4879987ea02201f32d763f52bee8b3362b2150d66a45c58fbe215160a4c271b4de17658e59d6e",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c4020000000001000000011e070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221008c3060a17cb4799bc2290d692c8ae3d4a4ecbc7ed938d91709f7d3f4879987ea02201f32d763f52bee8b3362b2150d66a45c58fbe215160a4c271b4de17658e59d6e83473044022045658d7072665bd8721859ea877d91587af138f9d3d6bc29339b972173521bb702205ccba02e9a2abd737af16abcfca3d9fd3fe6e0544e0efafddb634f3e30369c9e012001010101010101010101010101010101010101010101010101010101010101018d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6851b2756800000000"
},
{
"RemoteSigHex": "3044022030fa4ca0a8dfd47112aa04dfa76f5b30d1ec593b5d76d7c0f986603fb61c3a6d02203ae477dfbb5fba5959302faeb04b4e66cef2030e1ac8826e7034865059962b00",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c403000000000100000001e0120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022030fa4ca0a8dfd47112aa04dfa76f5b30d1ec593b5d76d7c0f986603fb61c3a6d02203ae477dfbb5fba5959302faeb04b4e66cef2030e1ac8826e7034865059962b0083483045022100a6408b8db488d9f0a3bfd54087e447d19be77e54c849ac388c99b0d68fe90921022023052d488d814ee1c94a27df75aaa9b97a4fd789a5eb1ee989ee2a30663f447f01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6851b27568f9010000"
},
{
"RemoteSigHex": "304402203ba5761a9a145470c62beef6984b7c3d76e9f1ec9f2b69fa8ccc5f47781e25b202205913220be6eb80a436f78d9e1c8c048f41b7490e9f91e862f4a473c9a8cac022",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c404000000000100000001e0120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402203ba5761a9a145470c62beef6984b7c3d76e9f1ec9f2b69fa8ccc5f47781e25b202205913220be6eb80a436f78d9e1c8c048f41b7490e9f91e862f4a473c9a8cac0228348304502210091f2bb95774acb4d51986d11c917fa7f51a39fe38b8aa880b430c9e629389ee202201e6bc67849075ec0fc8b5205380069b91097a4ad7a5bc1183384519ba05b3a2401008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6851b27568fa010000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80074a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994d007000000000000220020fe0598d74fee2205cc3672e6e6647706b4f3099713b4661b62482c3addd04a5e881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994aad9c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100b4014970d9d7962853f3f85196144671d7d5d87426250f0a5fdaf9a55292e92502205360910c9abb397467e19dbd63d081deb4a3240903114c98cec0a23591b79b7601473044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c72501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c725"
} }
] ]
+23
View File
@@ -237,5 +237,28 @@
"HtlcDescs": [], "HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220", "ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" "RemoteSigHex": "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e"
},
{
"Name": "commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage",
"LocalBalance": 6987999999,
"RemoteBalance": 3000000000,
"FeePerKw": 253,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "30450221008117770f1d750a8af7a23f27c51a22f66386ea8f074115d0feeac7fe0f077f6102203d4d8dab53ef695b634786224ae5138c81d223b0493fd4637a448e31f734bb30",
"ResolutionTxHex": "0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb9000000000000000000011f070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221008117770f1d750a8af7a23f27c51a22f66386ea8f074115d0feeac7fe0f077f6102203d4d8dab53ef695b634786224ae5138c81d223b0493fd4637a448e31f734bb3001483045022100f2868e4380ac389960b96a8d01bd0d7ae845d24ad67993f1680e207864d5d3ae0220779ad0a943f73951d628a4f931c3044c51d393de1ab6f8283df41b68485f2b1b012001010101010101010101010101010101010101010101010101010101010101018a76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac686800000000"
},
{
"RemoteSigHex": "30440220620b37379e587447e7ee9c8eed9e71b2d2ec29cadc89a4b5411ee6f9b013ca1b02201aaf26d1a03d901425fc44562bff77d5645cb54498e4d9a56c39456825974a3d",
"ResolutionTxHex": "0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb901000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220620b37379e587447e7ee9c8eed9e71b2d2ec29cadc89a4b5411ee6f9b013ca1b02201aaf26d1a03d901425fc44562bff77d5645cb54498e4d9a56c39456825974a3d0147304402204db4a1266aa8f0df66d35cd8b4269feef47f1cccebf7fdbb2b64cef35d72d79f022021b6d270a6d623cfec4cd0186096596d8deb17505d8c1c9785c719e0dbfa7e8401008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868f9010000"
},
{
"RemoteSigHex": "30450221009353b1d7e6313dc57a3781671b7ed9dde668b30e6f438ff08b13580937264de202200654599d2b9839fe9c88553f7c164a517fc8f0892e942b615460325fee7e0747",
"ResolutionTxHex": "0200000000010129253160416b9b2a2ecc303421b7fd1dee52d2e0b08d1a697f3979608334dbb902000000000000000001e1120000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004830450221009353b1d7e6313dc57a3781671b7ed9dde668b30e6f438ff08b13580937264de202200654599d2b9839fe9c88553f7c164a517fc8f0892e942b615460325fee7e074701483045022100c11a3f26356524b556bdfaeeae80f3cdef2cdb1c42c49047014a56af6916e2cb0220230d0667dc5b86015056a2ae5158ff29cbaf9cde419677e058d23c59a0a07d3101008576a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6868fa010000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8005d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2d8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121b8813000000000000220020305c12e1a0bc21e283c131cea1c66d68857d28b7b2fce0a6fbc40c164852121bc0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110a69f6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100d25455151be075bae8b3400d0825341a3c25a1a5258b84ad2546c09539a83bc602203c1a4ac19c3ac415af7f6a98348f8d7e94fc0ab82dbed54c3c40134f465d027f0147304402206cda85b2811a211aa70fb74abf23303d87c4355ccf2c2c7954d4137c4fb26a830220719402ab3fef1cbaf42ba42fe437e9bed1e45f84547d603bf7af3fb88f50193301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "304402206cda85b2811a211aa70fb74abf23303d87c4355ccf2c2c7954d4137c4fb26a830220719402ab3fef1cbaf42ba42fe437e9bed1e45f84547d603bf7af3fb88f501933"
} }
] ]
+166
View File
@@ -0,0 +1,166 @@
[
{
"Name": "simple commitment tx with no HTLCs",
"LocalBalance": 7000000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 546,
"FeePerKw": 15000,
"UseTestHtlcs": false,
"HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80044a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a508b6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221008266ac6db5ea71aac3c95d97b0e172ff596844851a3216eb88382a8dddfd33d2022050e240974cfd5d708708b4365574517c18e7ae535ef732a3484d43d0d82be9f701483045022100f89034eba16b2be0e5581f750a0a6309192b75cce0f202f0ee2b4ec0cc394850022076c65dc507fe42276152b7a3d90e961e678adbe966e916ecfe85e64d430e75f301475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3045022100f89034eba16b2be0e5581f750a0a6309192b75cce0f202f0ee2b4ec0cc394850022076c65dc507fe42276152b7a3d90e961e678adbe966e916ecfe85e64d430e75f3"
},
{
"Name": "simple commitment tx with no HTLCs and single anchor",
"LocalBalance": 10000000000,
"RemoteBalance": 0,
"DustLimitSatoshis": 546,
"FeePerKw": 15000,
"UseTestHtlcs": false,
"HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f10529800000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022007cf6b405e9c9b4f527b0ecad9d8bb661fabb8b12abf7d1c0b3ad1855db3ed490220616d5c1eeadccc63bd775a131149455d62d95a42c2a1b01cc7821fc42dce7778014730440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a865801475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "30440220655bf909fb6fa81d086f1336ac72c97906dce29d1b166e305c99152d810e26e1022051f577faa46412c46707aaac46b65d50053550a66334e00a44af2706f27a8658"
},
{
"Name": "commitment tx with seven outputs untrimmed",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 546,
"FeePerKw": 644,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "30440220746dc89a593e1b50915db63359b50c3c404f8324f78075c87708c866ccefda2502202a11062012dc8607b17e8c46ea4eefdfcc6b89a35440de99432ba12788d7bb17",
"ResolutionTxHex": "02000000000101b8cefef62ea66f5178b9361b2371be0759cbc8c689bcfa7a8e6746d497ec221a02000000000100000001e8030000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220746dc89a593e1b50915db63359b50c3c404f8324f78075c87708c866ccefda2502202a11062012dc8607b17e8c46ea4eefdfcc6b89a35440de99432ba12788d7bb1783473044022036f77f88b49bd03dc16d3a015efcc7acffc2a93b213324035d77a716f79013ff02203c218eb882a40402a8bb05dbb751a442345a4e56307be76b214b6d4db9ed3c92012000000000000000000000000000000000000000000000000000000000000000008d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f401b175ac6851b2756800000000"
},
{
"RemoteSigHex": "3045022100a847bc3b8cf2441013725ae32dc589c419835d069afd6d7f3d9834d8be7cea6e02204ce9d35ec7f0788da80e4d62e810c4ccd13617e58fa10832e38fb7ae87bdf338",
"ResolutionTxHex": "02000000000101b8cefef62ea66f5178b9361b2371be0759cbc8c689bcfa7a8e6746d497ec221a03000000000100000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100a847bc3b8cf2441013725ae32dc589c419835d069afd6d7f3d9834d8be7cea6e02204ce9d35ec7f0788da80e4d62e810c4ccd13617e58fa10832e38fb7ae87bdf33883473044022026739b1adbfa34c485bf0e5a19e0cf7532f64545bcc5c95b95643ccc2d351e1902201743bb1b34f00e031cc15f6eff0f8ab764508d2681ff965ba62e76e540bca1e801008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6851b27568f6010000"
},
{
"RemoteSigHex": "3044022035977f2aa6d6ae12f1dae3366440faa17558e9c88c3188bfc8df7e276c6c65410220659f1f9070c725d9d46e43b99a36fc6d711d36069704add2d57fc1fa2818cf12",
"ResolutionTxHex": "02000000000101b8cefef62ea66f5178b9361b2371be0759cbc8c689bcfa7a8e6746d497ec221a04000000000100000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022035977f2aa6d6ae12f1dae3366440faa17558e9c88c3188bfc8df7e276c6c65410220659f1f9070c725d9d46e43b99a36fc6d711d36069704add2d57fc1fa2818cf12834730440220191ba44e57b1601a59d99f85971d4801b286d428de275487c87ceeb8df1a4811022002215875d92833df0c9615c9096cf97152f87139ed9f82718bbb5b8b3d312524012001010101010101010101010101010101010101010101010101010101010101018d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6851b2756800000000"
},
{
"RemoteSigHex": "3044022002f3ff9a31270092c214d3d5b8b4f826599404bba64b87f7536ef6324d41551b022079dc4cb25f7ecd84b49f5cce03eae7e2655b59f39d543765daef0d4f11c93fa2",
"ResolutionTxHex": "02000000000101b8cefef62ea66f5178b9361b2371be0759cbc8c689bcfa7a8e6746d497ec221a05000000000100000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022002f3ff9a31270092c214d3d5b8b4f826599404bba64b87f7536ef6324d41551b022079dc4cb25f7ecd84b49f5cce03eae7e2655b59f39d543765daef0d4f11c93fa283483045022100f03047e38bc0aae2d80d53424b8c1d1b8139120e2bf09ad31a2803978745e6e102205b74c0eef0b472710b98c77e619ee9d0cc47a9dd786f4f214a564f44d79f9b9a01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6851b27568f7010000"
},
{
"RemoteSigHex": "30440220547937564288f64fb3e3c945a1348b912471f0c1c4cc7dc8ceca15a4cbd299b6022053c4f8e30832b13dfbe31e4091e313428625e0b5ac61eecba93f8f11c1e26225",
"ResolutionTxHex": "02000000000101b8cefef62ea66f5178b9361b2371be0759cbc8c689bcfa7a8e6746d497ec221a06000000000100000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220547937564288f64fb3e3c945a1348b912471f0c1c4cc7dc8ceca15a4cbd299b6022053c4f8e30832b13dfbe31e4091e313428625e0b5ac61eecba93f8f11c1e2622583473044022022604660234aef9bd21284598ec50f070ac82a3a0152e0af5e98a02cd6e8976f022042b0b9112ee00806b856dff6de52a82c98b036a4fe14bb5fd2926725e2fc8191012004040404040404040404040404040404040404040404040404040404040404048d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6851b2756800000000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80094a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994e80300000000000022002010f88bf09e56f14fb4543fd26e47b0db50ea5de9cf3fc46434792471082621aed0070000000000002200203e68115ae0b15b8de75b6c6bc9af5ac9f01391544e0870dae443a1e8fe7837ead007000000000000220020fe0598d74fee2205cc3672e6e6647706b4f3099713b4661b62482c3addd04a5eb80b000000000000220020f96d0334feb64a4f40eb272031d07afcb038db56aa57446d60308c9f8ccadef9a00f000000000000220020ce6e751274836ff59622a0d1e07f8831d80bd6730bd48581398bfadd2bb8da9ac0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a4f996a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100ef82a405364bfc4007e63a7cc82925a513d79065bdbc216d60b6a4223a323f8a02200716730b8561f3c6d362eaf47f202e99fb30d0557b61b92b5f9134f8e2de368101483045022100e0106830467a558c07544a3de7715610c1147062e7d091deeebe8b5c661cda9402202ad049c1a6d04834317a78483f723c205c9f638d17222aafc620800cc1b6ae3501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3045022100e0106830467a558c07544a3de7715610c1147062e7d091deeebe8b5c661cda9402202ad049c1a6d04834317a78483f723c205c9f638d17222aafc620800cc1b6ae35"
},
{
"Name": "commitment tx with six outputs untrimmed (minimum dust limit)",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 1001,
"FeePerKw": 645,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "3045022100e04d160a326432659fe9fb127304c1d348dfeaba840081bdc57d8efd902a48d8022008a824e7cf5492b97e4d9e03c06a09f822775a44f6b5b2533a2088904abfc282",
"ResolutionTxHex": "02000000000101104f394af4c4fad78337f95e3e9f802f4c0d86ab231853af09b285348561320002000000000100000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100e04d160a326432659fe9fb127304c1d348dfeaba840081bdc57d8efd902a48d8022008a824e7cf5492b97e4d9e03c06a09f822775a44f6b5b2533a2088904abfc28283483045022100b7c49846466b13b190ff739bbe3005c105482fc55539e55b1c561f76b6982b6c02200e5c35808619cf543c8405cff9fedd25f333a4a2f6f6d5e8af8150090c40ef0901008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6851b27568f6010000"
},
{
"RemoteSigHex": "3045022100fbdc3c367ce3bf30796025cc590ee1f2ce0e72ae1ac19f5986d6d0a4fc76211f02207e45ae9267e8e820d188569604f71d1abd11bd385d58853dd7dc034cdb3e9a6e",
"ResolutionTxHex": "02000000000101104f394af4c4fad78337f95e3e9f802f4c0d86ab231853af09b285348561320003000000000100000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100fbdc3c367ce3bf30796025cc590ee1f2ce0e72ae1ac19f5986d6d0a4fc76211f02207e45ae9267e8e820d188569604f71d1abd11bd385d58853dd7dc034cdb3e9a6e83483045022100d29330f24db213b262068706099b39c15fa7e070c3fcdf8836c09723fc4d365602203ce57d01e9f28601e461a0b5c4a50119b270bde8b70148d133a6849c70b115ac012001010101010101010101010101010101010101010101010101010101010101018d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6851b2756800000000"
},
{
"RemoteSigHex": "3044022066c5ef625cee3ddd2bc7b6bfb354b5834cf1cc6d52dd972fb41b7b225437ae4a022066cb85647df65c6b87a54e416dcdcca778a776c36a9643d2b5dc793c9b29f4c1",
"ResolutionTxHex": "02000000000101104f394af4c4fad78337f95e3e9f802f4c0d86ab231853af09b285348561320004000000000100000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022066c5ef625cee3ddd2bc7b6bfb354b5834cf1cc6d52dd972fb41b7b225437ae4a022066cb85647df65c6b87a54e416dcdcca778a776c36a9643d2b5dc793c9b29f4c18347304402202d4ce515cd9000ec37575972d70b8d24f73909fb7012e8ebd8c2066ef6fe187902202830b53e64ea565fecd0f398100691da6bb2a5cf9bb0d1926f1d71d05828a11e01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6851b27568f7010000"
},
{
"RemoteSigHex": "3044022022c7e11595c53ee89a57ca76baf0aed730da035952d6ab3fe6459f5eff3b337a022075e10cc5f5fd724a35ce4087a5d03cd616698626c69814032132b50bb97dc615",
"ResolutionTxHex": "02000000000101104f394af4c4fad78337f95e3e9f802f4c0d86ab231853af09b285348561320005000000000100000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022022c7e11595c53ee89a57ca76baf0aed730da035952d6ab3fe6459f5eff3b337a022075e10cc5f5fd724a35ce4087a5d03cd616698626c69814032132b50bb97dc61583483045022100b20cd63e0587d1711beaebda4730775c4ac8b8b2ec78fe18a0c44c3f168c25230220079abb7fc4924e2fca5950842e5b9e416735585026914570078c4ef62f286226012004040404040404040404040404040404040404040404040404040404040404048d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6851b2756800000000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80084a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994d0070000000000002200203e68115ae0b15b8de75b6c6bc9af5ac9f01391544e0870dae443a1e8fe7837ead007000000000000220020fe0598d74fee2205cc3672e6e6647706b4f3099713b4661b62482c3addd04a5eb80b000000000000220020f96d0334feb64a4f40eb272031d07afcb038db56aa57446d60308c9f8ccadef9a00f000000000000220020ce6e751274836ff59622a0d1e07f8831d80bd6730bd48581398bfadd2bb8da9ac0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994abc996a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100d57697c707b6f6d053febf24b98e8989f186eea42e37e9e91663ec2c70bb8f70022079b0715a472118f262f43016a674f59c015d9cafccec885968e76d9d9c5d005101473044022025d97466c8049e955a5afce28e322f4b34d2561118e52332fb400f9b908cc0a402205dc6fba3a0d67ee142c428c535580cd1f2ff42e2f89b47e0c8a01847caffc31201475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3044022025d97466c8049e955a5afce28e322f4b34d2561118e52332fb400f9b908cc0a402205dc6fba3a0d67ee142c428c535580cd1f2ff42e2f89b47e0c8a01847caffc312"
},
{
"Name": "commitment tx with four outputs untrimmed (minimum dust limit)",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 2001,
"FeePerKw": 2185,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "304402206870514a72ad6e723ff7f1e0370d7a33c1cd2a0b9272674143ebaf6a1d02dee102205bd953c34faf5e7322e9a1c0103581cb090280fda4f1039ee8552668afa90ebb",
"ResolutionTxHex": "02000000000101ac13a7715f80b8e52dda43c6929cade5521bdced3a405da02b443f1ffb1e33cc02000000000100000001b80b0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402206870514a72ad6e723ff7f1e0370d7a33c1cd2a0b9272674143ebaf6a1d02dee102205bd953c34faf5e7322e9a1c0103581cb090280fda4f1039ee8552668afa90ebb834730440220669de9ca7910eff65a7773ebd14a9fc371fe88cde5b8e2a81609d85c87ac939b02201ac29472fa4067322e92d75b624942d60be5050139b20bb363db75be79eb946f01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9148a486ff2e31d6158bf39e2608864d63fefd09d5b88ac6851b27568f7010000"
},
{
"RemoteSigHex": "3045022100949e8dd938da56445b1cdfdebe1b7efea086edd05d89910d205a1e2e033ce47102202cbd68b5262ab144d9ec12653f87dfb0bb6bd05d1f58ae1e523f028eaefd7271",
"ResolutionTxHex": "02000000000101ac13a7715f80b8e52dda43c6929cade5521bdced3a405da02b443f1ffb1e33cc03000000000100000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100949e8dd938da56445b1cdfdebe1b7efea086edd05d89910d205a1e2e033ce47102202cbd68b5262ab144d9ec12653f87dfb0bb6bd05d1f58ae1e523f028eaefd727183483045022100e3104ed8b239f8019e5f0a1a73d7782a94a8c36e7984f476c3a0b3cb0e62e27902207e3d52884600985f8a2098e53a5c30dd6a5e857733acfaa07ab2162421ed2688012004040404040404040404040404040404040404040404040404040404040404048d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6851b2756800000000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80064a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994b80b000000000000220020f96d0334feb64a4f40eb272031d07afcb038db56aa57446d60308c9f8ccadef9a00f000000000000220020ce6e751274836ff59622a0d1e07f8831d80bd6730bd48581398bfadd2bb8da9ac0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994ac5916a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100cd8479cfe1edb1e5a1d487391e0451a469c7171e51e680183f19eb4321f20e9b02204eab7d5a6384b1b08e03baa6e4d9748dfd2b5ab2bae7e39604a0d0055bbffdd501473044022040f63a16148cf35c8d3d41827f5ae7f7c3746885bb64d4d1b895892a83812b3e02202fcf95c2bf02c466163b3fa3ced6a24926fbb4035095a96842ef516e86ba54c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3044022040f63a16148cf35c8d3d41827f5ae7f7c3746885bb64d4d1b895892a83812b3e02202fcf95c2bf02c466163b3fa3ced6a24926fbb4035095a96842ef516e86ba54c0"
},
{
"Name": "commitment tx with three outputs untrimmed (minimum dust limit)",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 3001,
"FeePerKw": 3687,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "3044022017b558a3cf5f0cb94269e2e927b29ed22bd2416abb8a7ce6de4d1256f359b93602202e9ca2b1a23ea3e69f433c704e327739e219804b8c188b1d52f74fd5a9de954c",
"ResolutionTxHex": "02000000000101542562b326c08e3a076d9cfca2be175041366591da334d8d513ff1686fd95a6002000000000100000001a00f0000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500473044022017b558a3cf5f0cb94269e2e927b29ed22bd2416abb8a7ce6de4d1256f359b93602202e9ca2b1a23ea3e69f433c704e327739e219804b8c188b1d52f74fd5a9de954c83483045022100af7a8b7c7ff2080c68995254cb66d64d9954edcc5baac3bb4f27ed2d29aaa6120220421c27da7a60574a9263f271e0f3bd34594ec6011095190022b3b54596ea03de012004040404040404040404040404040404040404040404040404040404040404048d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a91418bc1a114ccf9c052d3d23e28d3b0a9d1227434288527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f801b175ac6851b2756800000000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80054a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994a00f000000000000220020ce6e751274836ff59622a0d1e07f8831d80bd6730bd48581398bfadd2bb8da9ac0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994aa28b6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100c970799bcb33f43179eb43b3378a0a61991cf2923f69b36ef12548c3df0e6d500220413dc27d2e39ee583093adfcb7799be680141738babb31cc7b0669a777a31f5d01483045022100ad6c71569856b2d7ff42e838b4abe74a713426b37f22fa667a195a4c88908c6902202b37272b02a42dc6d9f4f82cab3eaf84ac882d9ed762859e1e75455c2c22837701475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3045022100ad6c71569856b2d7ff42e838b4abe74a713426b37f22fa667a195a4c88908c6902202b37272b02a42dc6d9f4f82cab3eaf84ac882d9ed762859e1e75455c2c228377"
},
{
"Name": "commitment tx with two outputs untrimmed (minimum dust limit)",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 4001,
"FeePerKw": 4894,
"UseTestHtlcs": true,
"HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80044a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994ad0886a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004830450221009f16ac85d232e4eddb3fcd750a68ebf0b58e3356eaada45d3513ede7e817bf4c02207c2b043b4e5f971261975406cb955219fa56bffe5d834a833694b5abc1ce4cfd01483045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b9501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3045022100e784a66b1588575801e237d35e510fd92a81ae3a4a2a1b90c031ad803d07b3f3022021bc5f16501f167607d63b681442da193eb0a76b4b7fd25c2ed4f8b28fd35b95"
},
{
"Name": "commitment tx with one output untrimmed (minimum dust limit)",
"LocalBalance": 6988000000,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 4001,
"FeePerKw": 6216010,
"UseTestHtlcs": true,
"HtlcDescs": [],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80024a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994a04004830450221009ad80792e3038fe6968d12ff23e6888a565c3ddd065037f357445f01675d63f3022018384915e5f1f4ae157e15debf4f49b61c8d9d2b073c7d6f97c4a68caa3ed4c1014830450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "30450221008fd5dbff02e4b59020d4cd23a3c30d3e287065fda75a0a09b402980adf68ccda022001e0b8b620cd915ddff11f1de32addf23d81d51b90e6841b2cb8dcaf3faa5ecf"
},
{
"Name": "commitment tx with 3 htlc outputs, 2 offered having the same amount and preimage",
"LocalBalance": 6987999999,
"RemoteBalance": 3000000000,
"DustLimitSatoshis": 546,
"FeePerKw": 253,
"UseTestHtlcs": true,
"HtlcDescs": [
{
"RemoteSigHex": "30440220078fe5343dab88c348a3a8a9c1a9293259dbf35507ae971702cc39dd623ea9af022011ed0c0f35243cd0bb4d9ca3c772379b2b5f4af93140e9fdc5600dfec1cdb0c2",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c402000000000100000001d0070000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e05004730440220078fe5343dab88c348a3a8a9c1a9293259dbf35507ae971702cc39dd623ea9af022011ed0c0f35243cd0bb4d9ca3c772379b2b5f4af93140e9fdc5600dfec1cdb0c28347304402205df665e2908c7690d2d33eb70e6e119958c28febe141a94ed0dd9a55ce7c8cfc0220364d02663a5d019af35c5cd5fda9465d985d85bbd12db207738d61163449a424012001010101010101010101010101010101010101010101010101010101010101018d76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6851b2756800000000"
},
{
"RemoteSigHex": "304402202df6bf0f98a42cfd0172a16bded7d1b16c14f5f42ba23f5c54648c14b647531302200fe1508626817f23925bb56951d5e4b2654c751743ab6db48a6cce7dda17c01c",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c40300000000010000000188130000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e050047304402202df6bf0f98a42cfd0172a16bded7d1b16c14f5f42ba23f5c54648c14b647531302200fe1508626817f23925bb56951d5e4b2654c751743ab6db48a6cce7dda17c01c8347304402203f99ec05cdd89558a23683b471c1dcce8f6a92295f1fff3b0b5d21be4d4f97ea022019d29070690fc2c126fe27cc4ab2f503f289d362721b2efa7418e7fddb939a5b01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6851b27568f9010000"
},
{
"RemoteSigHex": "3045022100bd206b420c495f3aa714d3ea4766cbe95441deacb5d2f737f1913349aee7c2ae02200249d2c950dd3b15326bf378ae5d2b871d33d6737f5d70735f3de8383140f2a1",
"ResolutionTxHex": "020000000001013d060d0305c9616eaabc21d41fae85bcb5477b5d7f1c92aa429cf15339bbe1c40400000000010000000188130000000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0500483045022100bd206b420c495f3aa714d3ea4766cbe95441deacb5d2f737f1913349aee7c2ae02200249d2c950dd3b15326bf378ae5d2b871d33d6737f5d70735f3de8383140f2a183483045022100f2cd35e385b9b7e15b92a5d78d120b6b2c5af4e974bc01e884c5facb3bb5966c0220706e0506477ce809a40022d6de8e041e9ef13136c45abee9c36f58a01fdb188b01008876a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a9142002cc93ebefbb1b73f0af055dcc27a0b504ad7688ac6851b27568fa010000"
}
],
"ExpectedCommitmentTxHex": "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b80074a010000000000002200202b1b5854183c12d3316565972c4668929d314d81c5dcdbb21cb45fe8a9a8114f4a01000000000000220020e9e86e4823faa62e222ebc858a226636856158f07e69898da3b0d1af0ddb3994d007000000000000220020fe0598d74fee2205cc3672e6e6647706b4f3099713b4661b62482c3addd04a5e881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7881300000000000022002018e40f9072c44350f134bdc887bab4d9bdfc8aa468a25616c80e21757ba5dac7c0c62d0000000000220020f3394e1e619b0eca1f91be2fb5ab4dfc59ba5b84ebe014ad1d43a564d012994aad9c6a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400483045022100b4014970d9d7962853f3f85196144671d7d5d87426250f0a5fdaf9a55292e92502205360910c9abb397467e19dbd63d081deb4a3240903114c98cec0a23591b79b7601473044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c72501475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220",
"RemoteSigHex": "3044022027b38dfb654c34032ffb70bb43022981652fce923cbbe3cbe7394e2ade8b34230220584195b78da6e25c2e8da6b4308d9db25b65b64975db9266163ef592abb7c725"
}
]
+99 -46
View File
@@ -11,6 +11,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@@ -93,41 +94,80 @@ func newTestContext(t *testing.T) *testContext {
return tc return tc
} }
var testHtlcs = []struct { type htlc struct {
incoming bool incoming bool
amount lnwire.MilliSatoshi amount lnwire.MilliSatoshi
expiry uint32 expiry uint32
preimage string preimage string
}{ }
var testHtlcsSet1 = []htlc{
// htlc 0.
{ {
incoming: true, incoming: true,
amount: 1000000, amount: 1000000,
expiry: 500, expiry: 500,
preimage: "0000000000000000000000000000000000000000000000000000000000000000", preimage: "0000000000000000000000000000000000000000000000000" +
"000000000000000",
}, },
// htlc 1.
{ {
incoming: true, incoming: true,
amount: 2000000, amount: 2000000,
expiry: 501, expiry: 501,
preimage: "0101010101010101010101010101010101010101010101010101010101010101", preimage: "0101010101010101010101010101010101010101010101010" +
"101010101010101",
}, },
// htlc 2.
{ {
incoming: false, incoming: false,
amount: 2000000, amount: 2000000,
expiry: 502, expiry: 502,
preimage: "0202020202020202020202020202020202020202020202020202020202020202", preimage: "0202020202020202020202020202020202020202020202020" +
"202020202020202",
}, },
// htlc 3.
{ {
incoming: false, incoming: false,
amount: 3000000, amount: 3000000,
expiry: 503, expiry: 503,
preimage: "0303030303030303030303030303030303030303030303030303030303030303", preimage: "03030303030303030303030303030303030303030303030303" +
"03030303030303",
}, },
// htlc 4.
{ {
incoming: true, incoming: true,
amount: 4000000, amount: 4000000,
expiry: 504, expiry: 504,
preimage: "0404040404040404040404040404040404040404040404040404040404040404", preimage: "0404040404040404040404040404040404040404040404040" +
"404040404040404",
},
}
var testHtlcsSet2 = []htlc{
// htlc 1.
{
incoming: true,
amount: 2000000,
expiry: 501,
preimage: "01010101010101010101010101010101010101010101010101" +
"01010101010101",
},
// htlc 5.
{
incoming: false,
amount: 5000000,
expiry: 506,
preimage: "05050505050505050505050505050505050505050505050505" +
"05050505050505",
},
// htlc 6.
{
incoming: false,
amount: 5000001,
expiry: 505,
preimage: "05050505050505050505050505050505050505050505050505" +
"05050505050505",
}, },
} }
@@ -138,10 +178,11 @@ type htlcDesc struct {
} }
type testCase struct { type testCase struct {
Name string Name string
LocalBalance lnwire.MilliSatoshi LocalBalance lnwire.MilliSatoshi
RemoteBalance lnwire.MilliSatoshi RemoteBalance lnwire.MilliSatoshi
FeePerKw btcutil.Amount FeePerKw btcutil.Amount
DustLimitSatoshis btcutil.Amount
// UseTestHtlcs defined whether the fixed set of test htlc should be // UseTestHtlcs defined whether the fixed set of test htlc should be
// added to the channel before checking the commitment assertions. // added to the channel before checking the commitment assertions.
@@ -169,10 +210,18 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
jsonFile: "test_vectors_legacy.json", jsonFile: "test_vectors_legacy.json",
}, },
{ {
name: "anchors", name: "anchors",
chanType: channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit, chanType: channeldb.SingleFunderTweaklessBit |
channeldb.AnchorOutputsBit,
jsonFile: "test_vectors_anchors.json", jsonFile: "test_vectors_anchors.json",
}, },
{
name: "zero fee htlc tx",
chanType: channeldb.SingleFunderTweaklessBit |
channeldb.AnchorOutputsBit |
channeldb.ZeroHtlcTxFeeBit,
jsonFile: "test_vectors_zero_fee_htlc_tx.json",
},
} }
for _, set := range vectorSets { for _, set := range vectorSets {
@@ -186,25 +235,28 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
err = json.Unmarshal(jsonText, &testCases) err = json.Unmarshal(jsonText, &testCases)
require.NoError(t, err) require.NoError(t, err)
t.Run(set.name, func(t *testing.T) { for _, test := range testCases {
for _, test := range testCases { test := test
test := test name := fmt.Sprintf("%s-%s", set.name, test.Name)
t.Run(name, func(t *testing.T) {
t.Parallel()
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
testVectors(t, set.chanType, test) testVectors(t, set.chanType, test)
}) })
} })
}) }
} }
} }
// addTestHtlcs adds the test vector htlcs to the update logs of the local and // addTestHtlcs adds the test vector htlcs to the update logs of the local and
// remote node. // remote node.
func addTestHtlcs(t *testing.T, remote, func addTestHtlcs(t *testing.T, remote, local *LightningChannel,
local *LightningChannel) map[[20]byte]lntypes.Preimage { htlcSet []htlc) map[[20]byte]lntypes.Preimage {
hash160map := make(map[[20]byte]lntypes.Preimage) hash160map := make(map[[20]byte]lntypes.Preimage)
for _, htlc := range testHtlcs { for _, htlc := range htlcSet {
preimage, err := lntypes.MakePreimageFromStr(htlc.preimage) preimage, err := lntypes.MakePreimageFromStr(htlc.preimage)
require.NoError(t, err) require.NoError(t, err)
@@ -251,6 +303,18 @@ func addTestHtlcs(t *testing.T, remote,
func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
tc := newTestContext(t) tc := newTestContext(t)
// Determine which htlc set to use.
testHtlcs := testHtlcsSet1
if strings.Contains(test.Name, "same amount and preimage") {
testHtlcs = testHtlcsSet2
}
// If the test specifies a dust limit, then use it instead of the
// default.
if test.DustLimitSatoshis != 0 {
tc.dustLimit = test.DustLimitSatoshis
}
// Balances in the test vectors are before subtraction of in-flight // Balances in the test vectors are before subtraction of in-flight
// htlcs. Convert to spendable balances. // htlcs. Convert to spendable balances.
remoteBalance := test.RemoteBalance remoteBalance := test.RemoteBalance
@@ -266,6 +330,11 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
} }
} }
// Assert that the remote and local balances add up to the channel
// capacity.
require.EqualValues(t, lnwire.NewMSatFromSatoshis(tc.fundingAmount),
remoteBalance+localBalance)
// Set up a test channel on which the test commitment transaction is // Set up a test channel on which the test commitment transaction is
// going to be produced. // going to be produced.
remoteChannel, localChannel := createTestChannelsForVectors( remoteChannel, localChannel := createTestChannelsForVectors(
@@ -280,7 +349,9 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
// retrieve the corresponding preimage. // retrieve the corresponding preimage.
var hash160map map[[20]byte]lntypes.Preimage var hash160map map[[20]byte]lntypes.Preimage
if test.UseTestHtlcs { if test.UseTestHtlcs {
hash160map = addTestHtlcs(t, remoteChannel, localChannel) hash160map = addTestHtlcs(
t, remoteChannel, localChannel, testHtlcs,
)
} }
// Execute commit dance to arrive at the point where the local node has // Execute commit dance to arrive at the point where the local node has
@@ -300,10 +371,12 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment() remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteSig.ToSignatureBytes())) require.Equal(t, test.RemoteSigHex,
hex.EncodeToString(remoteSig.ToSignatureBytes()))
for i, sig := range remoteHtlcSigs { for i, sig := range remoteHtlcSigs {
require.Equal(t, test.HtlcDescs[i].RemoteSigHex, hex.EncodeToString(sig.ToSignatureBytes())) require.Equal(t, test.HtlcDescs[i].RemoteSigHex,
hex.EncodeToString(sig.ToSignatureBytes()))
} }
err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs) err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs)
@@ -321,7 +394,8 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
var txBytes bytes.Buffer var txBytes bytes.Buffer
require.NoError(t, forceCloseSum.CloseTx.Serialize(&txBytes)) require.NoError(t, forceCloseSum.CloseTx.Serialize(&txBytes))
require.Equal(t, test.ExpectedCommitmentTxHex, hex.EncodeToString(txBytes.Bytes())) require.Equal(t, test.ExpectedCommitmentTxHex,
hex.EncodeToString(txBytes.Bytes()))
// Obtain the second level transactions that the local node's channel // Obtain the second level transactions that the local node's channel
// state machine has produced. Store them in a map indexed by commit tx // state machine has produced. Store them in a map indexed by commit tx
@@ -374,27 +448,6 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
} }
} }
// htlcViewFromHTLCs constructs an htlcView of PaymentDescriptors from a slice
// of channeldb.HTLC structs.
func htlcViewFromHTLCs(htlcs []channeldb.HTLC) *htlcView {
var theHTLCView htlcView
for _, htlc := range htlcs {
paymentDesc := &PaymentDescriptor{
RHash: htlc.RHash,
Timeout: htlc.RefundTimeout,
Amount: htlc.Amt,
}
if htlc.Incoming {
theHTLCView.theirUpdates =
append(theHTLCView.theirUpdates, paymentDesc)
} else {
theHTLCView.ourUpdates =
append(theHTLCView.ourUpdates, paymentDesc)
}
}
return &theHTLCView
}
func TestCommitTxStateHint(t *testing.T) { func TestCommitTxStateHint(t *testing.T) {
t.Parallel() t.Parallel()
@@ -8,12 +8,12 @@ import (
"github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tlv"
) )
// FundingLocked is the message that both parties to a new channel creation // ChannelReady is the message that both parties to a new channel creation
// send once they have observed the funding transaction being confirmed on the // send once they have observed the funding transaction being confirmed on the
// blockchain. FundingLocked contains the signatures necessary for the channel // blockchain. ChannelReady contains the signatures necessary for the channel
// participants to advertise the existence of the channel to the rest of the // participants to advertise the existence of the channel to the rest of the
// network. // network.
type FundingLocked struct { type ChannelReady struct {
// ChanID is the outpoint of the channel's funding transaction. This // ChanID is the outpoint of the channel's funding transaction. This
// can be used to query for the channel in the database. // can be used to query for the channel in the database.
ChanID ChannelID ChanID ChannelID
@@ -33,26 +33,26 @@ type FundingLocked struct {
ExtraData ExtraOpaqueData ExtraData ExtraOpaqueData
} }
// NewFundingLocked creates a new FundingLocked message, populating it with the // NewChannelReady creates a new ChannelReady message, populating it with the
// necessary IDs and revocation secret. // necessary IDs and revocation secret.
func NewFundingLocked(cid ChannelID, npcp *btcec.PublicKey) *FundingLocked { func NewChannelReady(cid ChannelID, npcp *btcec.PublicKey) *ChannelReady {
return &FundingLocked{ return &ChannelReady{
ChanID: cid, ChanID: cid,
NextPerCommitmentPoint: npcp, NextPerCommitmentPoint: npcp,
ExtraData: make([]byte, 0), ExtraData: make([]byte, 0),
} }
} }
// A compile time check to ensure FundingLocked implements the lnwire.Message // A compile time check to ensure ChannelReady implements the lnwire.Message
// interface. // interface.
var _ Message = (*FundingLocked)(nil) var _ Message = (*ChannelReady)(nil)
// Decode deserializes the serialized FundingLocked message stored in the // Decode deserializes the serialized ChannelReady message stored in the
// passed io.Reader into the target FundingLocked using the deserialization // passed io.Reader into the target ChannelReady using the deserialization
// rules defined by the passed protocol version. // rules defined by the passed protocol version.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { func (c *ChannelReady) Decode(r io.Reader, pver uint32) error {
// Read all the mandatory fields in the message. // Read all the mandatory fields in the message.
err := ReadElements(r, err := ReadElements(r,
&c.ChanID, &c.ChanID,
@@ -80,12 +80,12 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error {
return nil return nil
} }
// Encode serializes the target FundingLocked message into the passed io.Writer // Encode serializes the target ChannelReady message into the passed io.Writer
// implementation. Serialization will observe the rules defined by the passed // implementation. Serialization will observe the rules defined by the passed
// protocol version. // protocol version.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { func (c *ChannelReady) Encode(w *bytes.Buffer, pver uint32) error {
if err := WriteChannelID(w, c.ChanID); err != nil { if err := WriteChannelID(w, c.ChanID); err != nil {
return err return err
} }
@@ -107,9 +107,9 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error {
} }
// MsgType returns the uint32 code which uniquely identifies this message as a // MsgType returns the uint32 code which uniquely identifies this message as a
// FundingLocked message on the wire. // ChannelReady message on the wire.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (c *FundingLocked) MsgType() MessageType { func (c *ChannelReady) MsgType() MessageType {
return MsgFundingLocked return MsgChannelReady
} }
+2 -2
View File
@@ -169,12 +169,12 @@ const (
// ScidAliasRequired is a required feature bit that signals that the // ScidAliasRequired is a required feature bit that signals that the
// node requires understanding of ShortChannelID aliases in the TLV // node requires understanding of ShortChannelID aliases in the TLV
// segment of the funding_locked message. // segment of the channel_ready message.
ScidAliasRequired FeatureBit = 46 ScidAliasRequired FeatureBit = 46
// ScidAliasOptional is an optional feature bit that signals that the // ScidAliasOptional is an optional feature bit that signals that the
// node understands ShortChannelID aliases in the TLV segment of the // node understands ShortChannelID aliases in the TLV segment of the
// funding_locked message. // channel_ready message.
ScidAliasOptional FeatureBit = 47 ScidAliasOptional FeatureBit = 47
// PaymentMetadataRequired is a required bit that denotes that if an // PaymentMetadataRequired is a required bit that denotes that if an
+3 -3
View File
@@ -286,10 +286,10 @@ func FuzzFundingCreated(f *testing.F) {
}) })
} }
func FuzzFundingLocked(f *testing.F) { func FuzzChannelReady(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
// Prefix with MsgFundingLocked. // Prefix with MsgChannelReady.
data = prefixWithMsgType(data, MsgFundingLocked) data = prefixWithMsgType(data, MsgChannelReady)
// Pass the message into our general fuzz harness for wire // Pass the message into our general fuzz harness for wire
// messages! // messages!
+4 -5
View File
@@ -566,8 +566,7 @@ func TestLightningWireProtocol(t *testing.T) {
v[0] = reflect.ValueOf(req) v[0] = reflect.ValueOf(req)
}, },
MsgFundingLocked: func(v []reflect.Value, r *rand.Rand) { MsgChannelReady: func(v []reflect.Value, r *rand.Rand) {
var c [32]byte var c [32]byte
if _, err := r.Read(c[:]); err != nil { if _, err := r.Read(c[:]); err != nil {
t.Fatalf("unable to generate chan id: %v", err) t.Fatalf("unable to generate chan id: %v", err)
@@ -580,7 +579,7 @@ func TestLightningWireProtocol(t *testing.T) {
return return
} }
req := NewFundingLocked(ChannelID(c), pubKey) req := NewChannelReady(ChannelID(c), pubKey)
v[0] = reflect.ValueOf(*req) v[0] = reflect.ValueOf(*req)
}, },
@@ -1013,8 +1012,8 @@ func TestLightningWireProtocol(t *testing.T) {
}, },
}, },
{ {
msgType: MsgFundingLocked, msgType: MsgChannelReady,
scenario: func(m FundingLocked) bool { scenario: func(m ChannelReady) bool {
return mainScenario(&m) return mainScenario(&m)
}, },
}, },
+5 -5
View File
@@ -31,7 +31,7 @@ const (
MsgAcceptChannel = 33 MsgAcceptChannel = 33
MsgFundingCreated = 34 MsgFundingCreated = 34
MsgFundingSigned = 35 MsgFundingSigned = 35
MsgFundingLocked = 36 MsgChannelReady = 36
MsgShutdown = 38 MsgShutdown = 38
MsgClosingSigned = 39 MsgClosingSigned = 39
MsgUpdateAddHTLC = 128 MsgUpdateAddHTLC = 128
@@ -88,8 +88,8 @@ func (t MessageType) String() string {
return "MsgFundingCreated" return "MsgFundingCreated"
case MsgFundingSigned: case MsgFundingSigned:
return "MsgFundingSigned" return "MsgFundingSigned"
case MsgFundingLocked: case MsgChannelReady:
return "FundingLocked" return "ChannelReady"
case MsgShutdown: case MsgShutdown:
return "Shutdown" return "Shutdown"
case MsgClosingSigned: case MsgClosingSigned:
@@ -190,8 +190,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) {
msg = &FundingCreated{} msg = &FundingCreated{}
case MsgFundingSigned: case MsgFundingSigned:
msg = &FundingSigned{} msg = &FundingSigned{}
case MsgFundingLocked: case MsgChannelReady:
msg = &FundingLocked{} msg = &ChannelReady{}
case MsgShutdown: case MsgShutdown:
msg = &Shutdown{} msg = &Shutdown{}
case MsgClosingSigned: case MsgClosingSigned:
+3 -3
View File
@@ -268,7 +268,7 @@ func makeAllMessages(t testing.TB, r *rand.Rand) []lnwire.Message {
msgAll = append(msgAll, newMsgAcceptChannel(t, r)) msgAll = append(msgAll, newMsgAcceptChannel(t, r))
msgAll = append(msgAll, newMsgFundingCreated(t, r)) msgAll = append(msgAll, newMsgFundingCreated(t, r))
msgAll = append(msgAll, newMsgFundingSigned(t, r)) msgAll = append(msgAll, newMsgFundingSigned(t, r))
msgAll = append(msgAll, newMsgFundingLocked(t, r)) msgAll = append(msgAll, newMsgChannelReady(t, r))
msgAll = append(msgAll, newMsgShutdown(t, r)) msgAll = append(msgAll, newMsgShutdown(t, r))
msgAll = append(msgAll, newMsgClosingSigned(t, r)) msgAll = append(msgAll, newMsgClosingSigned(t, r))
msgAll = append(msgAll, newMsgUpdateAddHTLC(t, r)) msgAll = append(msgAll, newMsgUpdateAddHTLC(t, r))
@@ -442,7 +442,7 @@ func newMsgFundingSigned(t testing.TB, r io.Reader) *lnwire.FundingSigned {
return msg return msg
} }
func newMsgFundingLocked(t testing.TB, r io.Reader) *lnwire.FundingLocked { func newMsgChannelReady(t testing.TB, r io.Reader) *lnwire.ChannelReady {
t.Helper() t.Helper()
var c [32]byte var c [32]byte
@@ -452,7 +452,7 @@ func newMsgFundingLocked(t testing.TB, r io.Reader) *lnwire.FundingLocked {
pubKey := randPubKey(t) pubKey := randPubKey(t)
msg := lnwire.NewFundingLocked(lnwire.ChannelID(c), pubKey) msg := lnwire.NewChannelReady(lnwire.ChannelID(c), pubKey)
msg.ExtraData = createExtraData(t, r) msg.ExtraData = createExtraData(t, r)
return msg return msg
+3
View File
@@ -116,3 +116,6 @@ endif
# Construct the integration test command with the added build flags. # Construct the integration test command with the added build flags.
ITEST_TAGS := $(DEV_TAGS) $(RPC_TAGS) integration $(backend) ITEST_TAGS := $(DEV_TAGS) $(RPC_TAGS) integration $(backend)
# Construct the coverage test command with the added build flags.
GOACC := $(GOACC_BIN) $(COVER_PKG) -- -tags="$(DEV_TAGS) $(LOG_TAGS)" $(TEST_FLAGS)
+12 -18
View File
@@ -13,16 +13,8 @@ To build for Android, you need either
### Go and Go mobile ### Go and Go mobile
First, follow the instructions to [install Go](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md#building-a-development-version-from-source). First, follow the instructions to [install
Go](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md#building-a-development-version-from-source).
Then, install [Go mobile](https://github.com/golang/go/wiki/Mobile) and
initialize it:
```shell
go install golang.org/x/mobile/cmd/gomobile@latest
go mod download golang.org/x/mobile
gomobile init
```
### Docker ### Docker
@@ -42,14 +34,8 @@ Note that `gomobile` only supports building projects from `$GOPATH` at this
point. However, with the introduction of Go modules, the source code files are point. However, with the introduction of Go modules, the source code files are
no longer installed there by default. no longer installed there by default.
To be able to do so, we must turn off module and using the now deprecated
`go get` command before turning modules back on again.
```shell ```shell
go env -w GO111MODULE="off" git clone https://github.com/lightningnetwork/lnd.git $GOPATH/src/github.com/lightningnetwork/lnd
go get github.com/lightningnetwork/lnd
go get golang.org/x/mobile/bind
go env -w GO111MODULE="on"
``` ```
Finally, let’s change directory to the newly created lnd folder inside `$GOPATH`: Finally, let’s change directory to the newly created lnd folder inside `$GOPATH`:
@@ -58,13 +44,21 @@ Finally, let’s change directory to the newly created lnd folder inside `$GOPAT
cd $GOPATH/src/github.com/lightningnetwork/lnd cd $GOPATH/src/github.com/lightningnetwork/lnd
``` ```
It is not recommended building from the master branch for mainnet. To checkout It is not recommended building from the master branch for mainnet. To check out
the latest tagged release of lnd, run the latest tagged release of lnd, run
```shell ```shell
git checkout $(git describe --match "v[0-9]*" --abbrev=0) git checkout $(git describe --match "v[0-9]*" --abbrev=0)
``` ```
Then, install [Go mobile](https://github.com/golang/go/wiki/Mobile) and
initialize it:
```shell
go install golang.org/x/mobile/cmd/gomobile
gomobile init
```
### iOS ### iOS
```shell ```shell
+1
View File
@@ -13,6 +13,7 @@ import (
flags "github.com/jessevdk/go-flags" flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/signal"
_ "golang.org/x/mobile/bind"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
+1 -1
View File
@@ -54,7 +54,7 @@ type ChanStatusConfig struct {
// IsChannelActive checks whether the channel identified by the provided // IsChannelActive checks whether the channel identified by the provided
// ChannelID is considered active. This should only return true if the // ChannelID is considered active. This should only return true if the
// channel has been sufficiently confirmed, the channel has received // channel has been sufficiently confirmed, the channel has received
// FundingLocked, and the remote peer is online. // ChannelReady, and the remote peer is online.
IsChannelActive func(lnwire.ChannelID) bool IsChannelActive func(lnwire.ChannelID) bool
// ApplyChannelUpdate processes new ChannelUpdates signed by our node by // ApplyChannelUpdate processes new ChannelUpdates signed by our node by
+14 -10
View File
@@ -717,11 +717,11 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
// manager are not aware of each other's states and if // manager are not aware of each other's states and if
// we did not do this, we would accept alias channel // we did not do this, we would accept alias channel
// updates after 6 confirmations, which would be buggy. // updates after 6 confirmations, which would be buggy.
// We'll queue a funding_locked message with the new // We'll queue a channel_ready message with the new
// alias. This should technically be done *after* the // alias. This should technically be done *after* the
// reestablish, but this behavior is pre-existing since // reestablish, but this behavior is pre-existing since
// the funding manager may already queue a // the funding manager may already queue a
// funding_locked before the channel_reestablish. // channel_ready before the channel_reestablish.
if !dbChan.IsPending { if !dbChan.IsPending {
aliasScid, err := p.cfg.RequestAlias() aliasScid, err := p.cfg.RequestAlias()
if err != nil { if err != nil {
@@ -740,18 +740,18 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
) )
// Fetch the second commitment point to send in // Fetch the second commitment point to send in
// the funding_locked message. // the channel_ready message.
second, err := dbChan.SecondCommitmentPoint() second, err := dbChan.SecondCommitmentPoint()
if err != nil { if err != nil {
return nil, err return nil, err
} }
fundingLockedMsg := lnwire.NewFundingLocked( channelReadyMsg := lnwire.NewChannelReady(
chanID, second, chanID, second,
) )
fundingLockedMsg.AliasScid = &aliasScid channelReadyMsg.AliasScid = &aliasScid
msgs = append(msgs, fundingLockedMsg) msgs = append(msgs, channelReadyMsg)
} }
// If we've negotiated the option-scid-alias feature // If we've negotiated the option-scid-alias feature
@@ -776,7 +776,8 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
chanID := lnwire.NewChanIDFromOutPoint(chanPoint) chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
p.log.Infof("loading ChannelPoint(%v)", chanPoint) p.log.Infof("Loading ChannelPoint(%v), isPending=%v",
chanPoint, lnChan.IsPending())
// Skip adding any permanently irreconcilable channels to the // Skip adding any permanently irreconcilable channels to the
// htlcswitch. // htlcswitch.
@@ -1318,6 +1319,8 @@ func (ms *msgStream) AddMsg(msg lnwire.Message) {
func waitUntilLinkActive(p *Brontide, func waitUntilLinkActive(p *Brontide,
cid lnwire.ChannelID) htlcswitch.ChannelUpdateHandler { cid lnwire.ChannelID) htlcswitch.ChannelUpdateHandler {
p.log.Tracef("Waiting for link=%v to be active", cid)
// Subscribe to receive channel events. // Subscribe to receive channel events.
// //
// NOTE: If the link is already active by SubscribeChannelEvents, then // NOTE: If the link is already active by SubscribeChannelEvents, then
@@ -1395,6 +1398,7 @@ func newChanMsgStream(p *Brontide, cid lnwire.ChannelID) *msgStream {
// If the link is still not active and the calling function // If the link is still not active and the calling function
// errored out, just return. // errored out, just return.
if chanLink == nil { if chanLink == nil {
p.log.Warnf("Link=%v is not active")
return return
} }
} }
@@ -1545,7 +1549,7 @@ out:
*lnwire.AcceptChannel, *lnwire.AcceptChannel,
*lnwire.FundingCreated, *lnwire.FundingCreated,
*lnwire.FundingSigned, *lnwire.FundingSigned,
*lnwire.FundingLocked: *lnwire.ChannelReady:
p.cfg.FundingManager.ProcessFundingMsg(msg, p) p.cfg.FundingManager.ProcessFundingMsg(msg, p)
@@ -1794,7 +1798,7 @@ func messageSummary(msg lnwire.Message) string {
case *lnwire.FundingSigned: case *lnwire.FundingSigned:
return fmt.Sprintf("chan_id=%v", msg.ChanID) return fmt.Sprintf("chan_id=%v", msg.ChanID)
case *lnwire.FundingLocked: case *lnwire.ChannelReady:
return fmt.Sprintf("chan_id=%v, next_point=%x", return fmt.Sprintf("chan_id=%v, next_point=%x",
msg.ChanID, msg.NextPerCommitmentPoint.SerializeCompressed()) msg.ChanID, msg.NextPerCommitmentPoint.SerializeCompressed())
@@ -2372,7 +2376,7 @@ out:
} }
p.log.Infof("Processing retransmitted "+ p.log.Infof("Processing retransmitted "+
"FundingLocked for ChannelPoint(%v)", "ChannelReady for ChannelPoint(%v)",
chanPoint) chanPoint)
nextRevoke := newChan.RemoteNextRevocation nextRevoke := newChan.RemoteNextRevocation
+5
View File
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
goErrors "errors" goErrors "errors"
"fmt" "fmt"
"math"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@@ -80,6 +81,10 @@ const (
// bitcoin (160 for litecoin), though we now clamp the lower end of this // bitcoin (160 for litecoin), though we now clamp the lower end of this
// range for user-chosen deltas to 18 blocks to be conservative. // range for user-chosen deltas to 18 blocks to be conservative.
MinCLTVDelta = 18 MinCLTVDelta = 18
// MaxCLTVDelta is the maximum CLTV value accepted by LND for all
// timelock deltas.
MaxCLTVDelta = math.MaxUint16
) )
var ( var (
+4
View File
@@ -772,6 +772,8 @@ func (r *InterceptorChain) rpcStateUnaryServerInterceptor() grpc.UnaryServerInte
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) { handler grpc.UnaryHandler) (interface{}, error) {
r.rpcsLog.Debugf("[%v] requested", info.FullMethod)
if err := r.checkRPCState(info.Server); err != nil { if err := r.checkRPCState(info.Server); err != nil {
return nil, err return nil, err
} }
@@ -786,6 +788,8 @@ func (r *InterceptorChain) rpcStateStreamServerInterceptor() grpc.StreamServerIn
return func(srv interface{}, ss grpc.ServerStream, return func(srv interface{}, ss grpc.ServerStream,
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
r.rpcsLog.Debugf("[%v] requested", info.FullMethod)
if err := r.checkRPCState(srv); err != nil { if err := r.checkRPCState(srv); err != nil {
return err return err
} }
+10 -20
View File
@@ -2941,8 +2941,6 @@ func (r *rpcServer) GetRecoveryInfo(ctx context.Context,
func (r *rpcServer) ListPeers(ctx context.Context, func (r *rpcServer) ListPeers(ctx context.Context,
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) { in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {
rpcsLog.Tracef("[listpeers] request")
serverPeers := r.server.Peers() serverPeers := r.server.Peers()
resp := &lnrpc.ListPeersResponse{ resp := &lnrpc.ListPeersResponse{
Peers: make([]*lnrpc.Peer, 0, len(serverPeers)), Peers: make([]*lnrpc.Peer, 0, len(serverPeers)),
@@ -3701,8 +3699,6 @@ func (r *rpcServer) PendingChannels(ctx context.Context,
in *lnrpc.PendingChannelsRequest) ( in *lnrpc.PendingChannelsRequest) (
*lnrpc.PendingChannelsResponse, error) { *lnrpc.PendingChannelsResponse, error) {
rpcsLog.Debugf("[pendingchannels]")
resp := &lnrpc.PendingChannelsResponse{} resp := &lnrpc.PendingChannelsResponse{}
// First, we find all the channels that will soon be opened. // First, we find all the channels that will soon be opened.
@@ -4101,6 +4097,7 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
nodePub := dbChannel.IdentityPub nodePub := dbChannel.IdentityPub
nodeID := hex.EncodeToString(nodePub.SerializeCompressed()) nodeID := hex.EncodeToString(nodePub.SerializeCompressed())
chanPoint := dbChannel.FundingOutpoint chanPoint := dbChannel.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(&chanPoint)
// As this is required for display purposes, we'll calculate // As this is required for display purposes, we'll calculate
// the weight of the commitment transaction. We also add on the // the weight of the commitment transaction. We also add on the
@@ -4136,6 +4133,10 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
// Fetch the set of aliases for the channel. // Fetch the set of aliases for the channel.
channelAliases := r.server.aliasMgr.GetAliases(dbScid) channelAliases := r.server.aliasMgr.GetAliases(dbScid)
// Fetch the peer alias. If one does not exist, errNoPeerAlias
// is returned and peerScidAlias will be an empty ShortChannelID.
peerScidAlias, _ := r.server.aliasMgr.GetPeerAlias(chanID)
channel := &lnrpc.Channel{ channel := &lnrpc.Channel{
Active: isActive, Active: isActive,
Private: isPrivate(dbChannel), Private: isPrivate(dbChannel),
@@ -4164,6 +4165,7 @@ func createRPCOpenChannel(r *rpcServer, dbChannel *channeldb.OpenChannel,
&dbChannel.RemoteChanCfg, &dbChannel.RemoteChanCfg,
), ),
AliasScids: make([]uint64, 0, len(channelAliases)), AliasScids: make([]uint64, 0, len(channelAliases)),
PeerScidAlias: peerScidAlias.ToUint64(),
ZeroConf: dbChannel.IsZeroConf(), ZeroConf: dbChannel.IsZeroConf(),
ZeroConfConfirmedScid: dbChannel.ZeroConfRealScid().ToUint64(), ZeroConfConfirmedScid: dbChannel.ZeroConfRealScid().ToUint64(),
// TODO: remove the following deprecated fields // TODO: remove the following deprecated fields
@@ -6409,8 +6411,6 @@ func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopol
func (r *rpcServer) ListPayments(ctx context.Context, func (r *rpcServer) ListPayments(ctx context.Context,
req *lnrpc.ListPaymentsRequest) (*lnrpc.ListPaymentsResponse, error) { req *lnrpc.ListPaymentsRequest) (*lnrpc.ListPaymentsResponse, error) {
rpcsLog.Debugf("[ListPayments]")
// If both dates are set, we check that the start date is less than the // If both dates are set, we check that the start date is less than the
// end date, otherwise we'll get an empty result. // end date, otherwise we'll get an empty result.
if req.CreationDateStart != 0 && req.CreationDateEnd != 0 { if req.CreationDateStart != 0 && req.CreationDateEnd != 0 {
@@ -6627,10 +6627,6 @@ const feeBase float64 = 1000000
func (r *rpcServer) FeeReport(ctx context.Context, func (r *rpcServer) FeeReport(ctx context.Context,
_ *lnrpc.FeeReportRequest) (*lnrpc.FeeReportResponse, error) { _ *lnrpc.FeeReportRequest) (*lnrpc.FeeReportResponse, error) {
// TODO(roasbeef): use UnaryInterceptor to add automated logging
rpcsLog.Debugf("[feereport]")
channelGraph := r.server.graphDB channelGraph := r.server.graphDB
selfNode, err := channelGraph.SourceNode() selfNode, err := channelGraph.SourceNode()
if err != nil { if err != nil {
@@ -6837,6 +6833,10 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
return nil, fmt.Errorf("time lock delta of %v is too small, "+ return nil, fmt.Errorf("time lock delta of %v is too small, "+
"minimum supported is %v", req.TimeLockDelta, "minimum supported is %v", req.TimeLockDelta,
minTimeLockDelta) minTimeLockDelta)
} else if req.TimeLockDelta > uint32(MaxTimeLockDelta) {
return nil, fmt.Errorf("time lock delta of %v is too big, "+
"maximum supported is %v", req.TimeLockDelta,
MaxTimeLockDelta)
} }
baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat) baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat)
@@ -6894,8 +6894,6 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context,
req *lnrpc.ForwardingHistoryRequest) (*lnrpc.ForwardingHistoryResponse, req *lnrpc.ForwardingHistoryRequest) (*lnrpc.ForwardingHistoryResponse,
error) { error) {
rpcsLog.Debugf("[forwardinghistory]")
// Before we perform the queries below, we'll instruct the switch to // Before we perform the queries below, we'll instruct the switch to
// flush any pending events to disk. This ensure we get a complete // flush any pending events to disk. This ensure we get a complete
// snapshot at this particular time. // snapshot at this particular time.
@@ -7426,8 +7424,6 @@ func (r *rpcServer) ChannelAcceptor(stream lnrpc.Lightning_ChannelAcceptorServer
func (r *rpcServer) BakeMacaroon(ctx context.Context, func (r *rpcServer) BakeMacaroon(ctx context.Context,
req *lnrpc.BakeMacaroonRequest) (*lnrpc.BakeMacaroonResponse, error) { req *lnrpc.BakeMacaroonRequest) (*lnrpc.BakeMacaroonResponse, error) {
rpcsLog.Debugf("[bakemacaroon]")
// If the --no-macaroons flag is used to start lnd, the macaroon service // If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't bake new macaroons. // is not initialized. Therefore we can't bake new macaroons.
if r.macService == nil { if r.macService == nil {
@@ -7514,8 +7510,6 @@ func (r *rpcServer) ListMacaroonIDs(ctx context.Context,
req *lnrpc.ListMacaroonIDsRequest) ( req *lnrpc.ListMacaroonIDsRequest) (
*lnrpc.ListMacaroonIDsResponse, error) { *lnrpc.ListMacaroonIDsResponse, error) {
rpcsLog.Debugf("[listmacaroonids]")
// If the --no-macaroons flag is used to start lnd, the macaroon service // If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't show any IDs. // is not initialized. Therefore we can't show any IDs.
if r.macService == nil { if r.macService == nil {
@@ -7546,8 +7540,6 @@ func (r *rpcServer) DeleteMacaroonID(ctx context.Context,
req *lnrpc.DeleteMacaroonIDRequest) ( req *lnrpc.DeleteMacaroonIDRequest) (
*lnrpc.DeleteMacaroonIDResponse, error) { *lnrpc.DeleteMacaroonIDResponse, error) {
rpcsLog.Debugf("[deletemacaroonid]")
// If the --no-macaroons flag is used to start lnd, the macaroon service // If the --no-macaroons flag is used to start lnd, the macaroon service
// is not initialized. Therefore we can't delete any IDs. // is not initialized. Therefore we can't delete any IDs.
if r.macService == nil { if r.macService == nil {
@@ -7577,8 +7569,6 @@ func (r *rpcServer) ListPermissions(_ context.Context,
_ *lnrpc.ListPermissionsRequest) (*lnrpc.ListPermissionsResponse, _ *lnrpc.ListPermissionsRequest) (*lnrpc.ListPermissionsResponse,
error) { error) {
rpcsLog.Debugf("[listpermissions]")
permissionMap := make(map[string]*lnrpc.MacaroonPermissionList) permissionMap := make(map[string]*lnrpc.MacaroonPermissionList)
for uri, perms := range r.interceptorChain.Permissions() { for uri, perms := range r.interceptorChain.Permissions() {
rpcPerms := make([]*lnrpc.MacaroonPermission, len(perms)) rpcPerms := make([]*lnrpc.MacaroonPermission, len(perms))
+9
View File
@@ -997,6 +997,15 @@ litecoin.node=ltcd
; supported at this time, if none are provided the tower will not be enabled. ; supported at this time, if none are provided the tower will not be enabled.
; wtclient.private-tower-uris= ; wtclient.private-tower-uris=
; The range over which to choose a random number of blocks to wait after the
; last channel of a session is closed before sending the DeleteSession message
; to the tower server. The default is currently 288. Note that setting this to
; a lower value will result in faster session cleanup _but_ that this comes
; along with reduced privacy from the tower server.
; wtclient.session-close-range=10
; The maximum number of updates to include in a tower session.
; wtclient.max-updates=1024
[healthcheck] [healthcheck]
+29
View File
@@ -1497,6 +1497,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
policy.SweepFeeRate = sweepRateSatPerVByte.FeePerKWeight() policy.SweepFeeRate = sweepRateSatPerVByte.FeePerKWeight()
} }
if cfg.WtClient.MaxUpdates != 0 {
policy.MaxUpdates = cfg.WtClient.MaxUpdates
}
sessionCloseRange := uint32(wtclient.DefaultSessionCloseRange)
if cfg.WtClient.SessionCloseRange != 0 {
sessionCloseRange = cfg.WtClient.SessionCloseRange
}
if err := policy.Validate(); err != nil { if err := policy.Validate(); err != nil {
return nil, err return nil, err
} }
@@ -1512,7 +1521,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
) )
} }
fetchClosedChannel := s.chanStateDB.FetchClosedChannelForID
s.towerClient, err = wtclient.New(&wtclient.Config{ s.towerClient, err = wtclient.New(&wtclient.Config{
FetchClosedChannel: fetchClosedChannel,
SessionCloseRange: sessionCloseRange,
ChainNotifier: s.cc.ChainNotifier,
SubscribeChannelEvents: func() (subscribe.Subscription,
error) {
return s.channelNotifier.
SubscribeChannelEvents()
},
Signer: cc.Wallet.Cfg.Signer, Signer: cc.Wallet.Cfg.Signer,
NewAddress: newSweepPkScriptGen(cc.Wallet), NewAddress: newSweepPkScriptGen(cc.Wallet),
SecretKeyRing: s.cc.KeyRing, SecretKeyRing: s.cc.KeyRing,
@@ -1536,6 +1556,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
blob.Type(blob.FlagAnchorChannel) blob.Type(blob.FlagAnchorChannel)
s.anchorTowerClient, err = wtclient.New(&wtclient.Config{ s.anchorTowerClient, err = wtclient.New(&wtclient.Config{
FetchClosedChannel: fetchClosedChannel,
SessionCloseRange: sessionCloseRange,
ChainNotifier: s.cc.ChainNotifier,
SubscribeChannelEvents: func() (subscribe.Subscription,
error) {
return s.channelNotifier.
SubscribeChannelEvents()
},
Signer: cc.Wallet.Cfg.Signer, Signer: cc.Wallet.Cfg.Signer,
NewAddress: newSweepPkScriptGen(cc.Wallet), NewAddress: newSweepPkScriptGen(cc.Wallet),
SecretKeyRing: s.cc.KeyRing, SecretKeyRing: s.cc.KeyRing,
+75 -1
View File
@@ -69,6 +69,12 @@ type AddressIterator interface {
// Reset clears the iterators state, and makes the address at the front // Reset clears the iterators state, and makes the address at the front
// of the list the next item to be returned. // of the list the next item to be returned.
Reset() Reset()
// Copy constructs a new AddressIterator that has the same addresses
// as this iterator.
//
// NOTE that the address locks are not expected to be copied.
Copy() AddressIterator
} }
// A compile-time check to ensure that addressIterator implements the // A compile-time check to ensure that addressIterator implements the
@@ -156,7 +162,15 @@ func (a *addressIterator) next(lock bool) (net.Addr, error) {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
// Set the next candidate to the subsequent element. // In-case currentTopAddr is nil (meaning that Reset has not yet been
// called), return an error indicating this.
if a.currentTopAddr == nil {
return nil, ErrAddressesExhausted
}
// Set the next candidate to the subsequent element. If we are at the
// end of the address list, this could mean setting currentTopAddr to
// nil.
a.currentTopAddr = a.currentTopAddr.Next() a.currentTopAddr = a.currentTopAddr.Next()
for a.currentTopAddr != nil { for a.currentTopAddr != nil {
@@ -166,9 +180,26 @@ func (a *addressIterator) next(lock bool) (net.Addr, error) {
// Check whether this address is still considered a candidate. // Check whether this address is still considered a candidate.
// If it's not, we'll proceed to the next. // If it's not, we'll proceed to the next.
candidate, ok := a.candidates[addrID] candidate, ok := a.candidates[addrID]
// If the address cannot be found in the candidate set, then
// this must mean that the Remove method was called for the
// address. The Remove method would have checked that the
// address is not the last one in the iterator and that it has
// no locks on it. It is therefor safe to remove.
if !ok { if !ok {
// Grab the next address candidate. This might be nil
// if the iterator is on the last item in the list.
nextCandidate := a.currentTopAddr.Next() nextCandidate := a.currentTopAddr.Next()
// Remove the address from the list that is no longer
// in the candidate set.
a.addrList.Remove(a.currentTopAddr) a.addrList.Remove(a.currentTopAddr)
// Set the current top to the next candidate. This might
// mean setting it to nil if the iterator is on its last
// item in which case the loop will be exited and an
// ErrAddressesExhausted exhausted error will be
// returned.
a.currentTopAddr = nextCandidate a.currentTopAddr = nextCandidate
continue continue
} }
@@ -224,9 +255,25 @@ func (a *addressIterator) peek(lock bool) net.Addr {
addrID := a.currentTopAddr.Value.(string) addrID := a.currentTopAddr.Value.(string)
candidate, ok := a.candidates[addrID] candidate, ok := a.candidates[addrID]
// If the address cannot be found in the candidate set, then
// this must mean that the Remove method was called for the
// address. The Remove method would have checked that the
// address is not the last one in the iterator and that it has
// no locks on it. It is therefor safe to remove.
if !ok { if !ok {
// Grab the next address candidate. This might be nil
// if the iterator is on the last item in the list.
nextCandidate := a.currentTopAddr.Next() nextCandidate := a.currentTopAddr.Next()
// Remove the address from the list that is no longer
// in the candidate set.
a.addrList.Remove(a.currentTopAddr) a.addrList.Remove(a.currentTopAddr)
// Set the current top to the next candidate. This might
// mean setting it to nil if the iterator is on its last
// item but this will be reset at the top of the for
// loop.
a.currentTopAddr = nextCandidate a.currentTopAddr = nextCandidate
continue continue
} }
@@ -324,6 +371,33 @@ func (a *addressIterator) GetAll() []net.Addr {
a.mu.Lock() a.mu.Lock()
defer a.mu.Unlock() defer a.mu.Unlock()
return a.getAllUnsafe()
}
// Copy constructs a new AddressIterator that has the same addresses
// as this iterator.
//
// NOTE that the address locks will not be copied.
func (a *addressIterator) Copy() AddressIterator {
a.mu.Lock()
defer a.mu.Unlock()
addrs := a.getAllUnsafe()
// Since newAddressIterator will only ever return an error if it is
// initialised with zero addresses, we can ignore the error here since
// we are initialising it with the set of addresses of this
// addressIterator which is by definition a non-empty list.
iter, _ := newAddressIterator(addrs...)
return iter
}
// getAllUnsafe returns a copy of all the addresses in the iterator.
//
// NOTE: this method is not thread safe and so must only be called once the
// addressIterator mutex is already being held.
func (a *addressIterator) getAllUnsafe() []net.Addr {
var addrs []net.Addr var addrs []net.Addr
cursor := a.addrList.Front() cursor := a.addrList.Front()
+190 -142
View File
@@ -10,179 +10,227 @@ import (
// TestAddrIterator tests the behaviour of the addressIterator. // TestAddrIterator tests the behaviour of the addressIterator.
func TestAddrIterator(t *testing.T) { func TestAddrIterator(t *testing.T) {
// Assert that an iterator can't be initialised with an empty address t.Parallel()
// list.
_, err := newAddressIterator()
require.ErrorContains(t, err, "must have at least one address")
// Prep some addresses to use in the subtests.
addr1, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8000") addr1, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8000")
require.NoError(t, err) require.NoError(t, err)
// Initialise the iterator with addr1.
iter, err := newAddressIterator(addr1)
require.NoError(t, err)
// Attempting to remove addr1 should fail now since it is the only
// address in the iterator.
iter.Add(addr1)
err = iter.Remove(addr1)
require.ErrorIs(t, err, wtdb.ErrLastTowerAddr)
// Adding a duplicate of addr1 and then calling Remove should still
// return an error.
err = iter.Remove(addr1)
require.ErrorIs(t, err, wtdb.ErrLastTowerAddr)
addr2, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8001") addr2, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8001")
require.NoError(t, err) require.NoError(t, err)
// Add addr2 to the iterator.
iter.Add(addr2)
// Check that peek returns addr1.
a1 := iter.Peek()
require.NoError(t, err)
require.Equal(t, addr1, a1)
// Calling peek multiple times should return the same result.
a1 = iter.Peek()
require.Equal(t, addr1, a1)
// Calling Next should now return addr2.
a2, err := iter.Next()
require.NoError(t, err)
require.Equal(t, addr2, a2)
// Assert that Peek now returns addr2.
a2 = iter.Peek()
require.NoError(t, err)
require.Equal(t, addr2, a2)
// Calling Next should result in reaching the end of th list.
_, err = iter.Next()
require.ErrorIs(t, err, ErrAddressesExhausted)
// Calling Peek now should reset the queue and return addr1.
a1 = iter.Peek()
require.Equal(t, addr1, a1)
// Wind the list to the end again so that we can test the Reset func.
_, err = iter.Next()
require.NoError(t, err)
_, err = iter.Next()
require.ErrorIs(t, err, ErrAddressesExhausted)
iter.Reset()
// Now Next should return addr 2.
a2, err = iter.Next()
require.NoError(t, err)
require.Equal(t, addr2, a2)
addr3, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8002") addr3, err := net.ResolveTCPAddr("tcp", "1.2.3.4:8002")
require.NoError(t, err) require.NoError(t, err)
// Add addr3 now to ensure that the iteration works even if we are t.Run("always at least one address", func(t *testing.T) {
// midway through the queue. t.Parallel()
iter.Add(addr3)
// Now Next should return addr 3. // Assert that an iterator can't be initialised with an empty
a3, err := iter.Next() // address list.
require.NoError(t, err) _, err := newAddressIterator()
require.Equal(t, addr3, a3) require.ErrorContains(t, err, "must have at least one address")
// Quickly test that GetAll correctly returns a copy of all the // Initialise the iterator with addr1.
// addresses in the iterator. iter, err := newAddressIterator(addr1)
addrList := iter.GetAll() require.NoError(t, err)
require.ElementsMatch(t, addrList, []net.Addr{addr1, addr2, addr3})
// Let's now remove addr3. // Attempting to remove addr1 should fail now since it is the
err = iter.Remove(addr3) // only address in the iterator.
require.NoError(t, err) iter.Add(addr1)
err = iter.Remove(addr1)
require.ErrorIs(t, err, wtdb.ErrLastTowerAddr)
// Since addr3 is gone, Peek should return addr1. // Adding a duplicate of addr1 and then calling Remove should
a1 = iter.Peek() // still return an error.
require.Equal(t, addr1, a1) err = iter.Remove(addr1)
require.ErrorIs(t, err, wtdb.ErrLastTowerAddr)
})
// Lastly, we will test the "locking" of addresses. t.Run("address iteration", func(t *testing.T) {
t.Parallel()
// First we test the locking of an address via the PeekAndLock function. // Initialise the iterator with addr1.
a1 = iter.PeekAndLock() iter, err := newAddressIterator(addr1)
require.Equal(t, addr1, a1) require.NoError(t, err)
require.True(t, iter.HasLocked())
// Assert that we can't remove addr1 if there is a lock on it. // Add addr2 to the iterator.
err = iter.Remove(addr1) iter.Add(addr2)
require.ErrorIs(t, err, ErrAddrInUse)
// Now release the lock on addr1. // Check that peek returns addr1.
iter.ReleaseLock(addr1) a1 := iter.Peek()
require.False(t, iter.HasLocked()) require.NoError(t, err)
require.Equal(t, addr1, a1)
// Since the lock has been released, we should now be able to remove // Calling peek multiple times should return the same result.
// addr1. a1 = iter.Peek()
err = iter.Remove(addr1) require.Equal(t, addr1, a1)
require.NoError(t, err)
// Now we test the locking of an address via the NextAndLock function. // Calling Next should now return addr2.
// To do this, we first re-add addr3. a2, err := iter.Next()
iter.Add(addr3) require.NoError(t, err)
require.Equal(t, addr2, a2)
a2, err = iter.NextAndLock() // Assert that Peek now returns addr2.
require.NoError(t, err) a2 = iter.Peek()
require.Equal(t, addr2, a2) require.NoError(t, err)
require.True(t, iter.HasLocked()) require.Equal(t, addr2, a2)
// Assert that we can't remove addr2 if there is a lock on it. // Calling Next should result in reaching the end of th list.
err = iter.Remove(addr2) _, err = iter.Next()
require.ErrorIs(t, err, ErrAddrInUse) require.ErrorIs(t, err, ErrAddressesExhausted)
// Now release the lock on addr2. // Calling Peek now should reset the queue and return addr1.
iter.ReleaseLock(addr2) a1 = iter.Peek()
require.False(t, iter.HasLocked()) require.Equal(t, addr1, a1)
// Since the lock has been released, we should now be able to remove // Wind the list to the end again so that we can test the Reset
// addr1. // func.
err = iter.Remove(addr2) _, err = iter.Next()
require.NoError(t, err) require.NoError(t, err)
// Only addr3 should still be left in the iterator. _, err = iter.Next()
addrList = iter.GetAll() require.ErrorIs(t, err, ErrAddressesExhausted)
require.Len(t, addrList, 1)
require.Contains(t, addrList, addr3)
// Ensure that HasLocked acts correctly in the case where more than one iter.Reset()
// address is being locked and unlock as well as the case where the same
// address is locked more than once.
require.False(t, iter.HasLocked()) // Now Next should return addr 2.
a2, err = iter.Next()
require.NoError(t, err)
require.Equal(t, addr2, a2)
a3 = iter.PeekAndLock() // Add addr3 now to ensure that the iteration works even if we
require.Equal(t, addr3, a3) // are midway through the queue.
require.True(t, iter.HasLocked()) iter.Add(addr3)
a3 = iter.PeekAndLock() // Now Next should return addr 3.
require.Equal(t, addr3, a3) a3, err := iter.Next()
require.True(t, iter.HasLocked()) require.NoError(t, err)
require.Equal(t, addr3, a3)
iter.Add(addr2) // Quickly test that GetAll correctly returns a copy of all the
a2, err = iter.NextAndLock() // addresses in the iterator.
require.NoError(t, err) addrList := iter.GetAll()
require.Equal(t, addr2, a2) require.ElementsMatch(
require.True(t, iter.HasLocked()) t, addrList, []net.Addr{addr1, addr2, addr3},
)
// Now release addr2 and asset that HasLock is still true. // Also check that an iterator constructed via the Copy method,
iter.ReleaseLock(addr2) // also contains all the expected addresses.
require.True(t, iter.HasLocked()) newIterAddrs := iter.Copy().GetAll()
require.ElementsMatch(
t, newIterAddrs, []net.Addr{addr1, addr2, addr3},
)
// Releasing one of the locks on addr3 now should still result in // Let's now remove addr3.
// HasLocked returning true. err = iter.Remove(addr3)
iter.ReleaseLock(addr3) require.NoError(t, err)
require.True(t, iter.HasLocked())
// Releasing it again should now result in should still result in // Since addr3 is gone, Peek should return addr1.
// HasLocked returning false. a1 = iter.Peek()
iter.ReleaseLock(addr3) require.Equal(t, addr1, a1)
require.False(t, iter.HasLocked()) })
t.Run("address locking", func(t *testing.T) {
t.Parallel()
// Initialise the iterator with addr1 and addr2.
iter, err := newAddressIterator(addr1, addr2)
require.NoError(t, err)
// First we test the locking of an address via the PeekAndLock
// function.
a1 := iter.PeekAndLock()
require.Equal(t, addr1, a1)
require.True(t, iter.HasLocked())
// Assert that we can't remove addr1 if there is a lock on it.
err = iter.Remove(addr1)
require.ErrorIs(t, err, ErrAddrInUse)
// Now release the lock on addr1.
iter.ReleaseLock(addr1)
require.False(t, iter.HasLocked())
// Since the lock has been released, we should now be able to
// remove addr1.
err = iter.Remove(addr1)
require.NoError(t, err)
// Now we test the locking of an address via the NextAndLock
// function. To do this, we first re-add addr3.
iter.Add(addr3)
a2, err := iter.NextAndLock()
require.NoError(t, err)
require.Equal(t, addr2, a2)
require.True(t, iter.HasLocked())
// Assert that we can't remove addr2 if there is a lock on it.
err = iter.Remove(addr2)
require.ErrorIs(t, err, ErrAddrInUse)
// Now release the lock on addr2.
iter.ReleaseLock(addr2)
require.False(t, iter.HasLocked())
// Since the lock has been released, we should now be able to
// remove addr1.
err = iter.Remove(addr2)
require.NoError(t, err)
// Only addr3 should still be left in the iterator.
addrList := iter.GetAll()
require.Len(t, addrList, 1)
require.Contains(t, addrList, addr3)
// Ensure that HasLocked acts correctly in the case where more
// than one address is being locked and unlock as well as the
// case where the same address is locked more than once.
require.False(t, iter.HasLocked())
a3 := iter.PeekAndLock()
require.Equal(t, addr3, a3)
require.True(t, iter.HasLocked())
a3 = iter.PeekAndLock()
require.Equal(t, addr3, a3)
require.True(t, iter.HasLocked())
iter.Add(addr2)
a2, err = iter.NextAndLock()
require.NoError(t, err)
require.Equal(t, addr2, a2)
require.True(t, iter.HasLocked())
// Now release addr2 and assert that HasLock is still true.
iter.ReleaseLock(addr2)
require.True(t, iter.HasLocked())
// Releasing one of the locks on addr3 now should still result
// in HasLocked returning true.
iter.ReleaseLock(addr3)
require.True(t, iter.HasLocked())
// Releasing it again should now result in should still result
// in HasLocked returning false.
iter.ReleaseLock(addr3)
require.False(t, iter.HasLocked())
})
t.Run("calling Next twice without Reset is safe", func(t *testing.T) {
t.Parallel()
// Initialise the iterator with addr1.
iter, err := newAddressIterator(addr1)
require.NoError(t, err)
a1 := iter.Peek()
require.Equal(t, addr1, a1)
_, err = iter.Next()
require.ErrorIs(t, err, ErrAddressesExhausted)
_, err = iter.Next()
require.ErrorIs(t, err, ErrAddressesExhausted)
})
} }
+19
View File
@@ -29,6 +29,10 @@ type TowerCandidateIterator interface {
// candidates available as long as they remain in the set. // candidates available as long as they remain in the set.
Reset() error Reset() error
// GetTower gets the tower with the given ID from the iterator. If no
// such tower is found then ErrTowerNotInIterator is returned.
GetTower(id wtdb.TowerID) (*Tower, error)
// Next returns the next candidate tower. The iterator is not required // Next returns the next candidate tower. The iterator is not required
// to return results in any particular order. If no more candidates are // to return results in any particular order. If no more candidates are
// available, ErrTowerCandidatesExhausted is returned. // available, ErrTowerCandidatesExhausted is returned.
@@ -76,6 +80,20 @@ func (t *towerListIterator) Reset() error {
return nil return nil
} }
// GetTower gets the tower with the given ID from the iterator. If no such tower
// is found then ErrTowerNotInIterator is returned.
func (t *towerListIterator) GetTower(id wtdb.TowerID) (*Tower, error) {
t.mu.Lock()
defer t.mu.Unlock()
tower, ok := t.candidates[id]
if !ok {
return nil, ErrTowerNotInIterator
}
return tower, nil
}
// Next returns the next candidate tower. This iterator will always return // Next returns the next candidate tower. This iterator will always return
// candidates in the order given when the iterator was instantiated. If no more // candidates in the order given when the iterator was instantiated. If no more
// candidates are available, ErrTowerCandidatesExhausted is returned. // candidates are available, ErrTowerCandidatesExhausted is returned.
@@ -127,6 +145,7 @@ func (t *towerListIterator) AddCandidate(candidate *Tower) {
for { for {
next, err := candidate.Addresses.Next() next, err := candidate.Addresses.Next()
if err != nil { if err != nil {
candidate.Addresses.Reset()
break break
} }
+22 -8
View File
@@ -52,14 +52,10 @@ func randTower(t *testing.T) *Tower {
func copyTower(t *testing.T, tower *Tower) *Tower { func copyTower(t *testing.T, tower *Tower) *Tower {
t.Helper() t.Helper()
addrs := tower.Addresses.GetAll()
addrIterator, err := newAddressIterator(addrs...)
require.NoError(t, err)
return &Tower{ return &Tower{
ID: tower.ID, ID: tower.ID,
IdentityKey: tower.IdentityKey, IdentityKey: tower.IdentityKey,
Addresses: addrIterator, Addresses: tower.Addresses.Copy(),
} }
} }
@@ -83,9 +79,15 @@ func assertNextCandidate(t *testing.T, i TowerCandidateIterator, c *Tower) {
tower, err := i.Next() tower, err := i.Next()
require.NoError(t, err) require.NoError(t, err)
require.True(t, tower.IdentityKey.IsEqual(c.IdentityKey)) assertTowersEqual(t, c, tower)
require.Equal(t, tower.ID, c.ID) }
require.Equal(t, tower.Addresses.GetAll(), c.Addresses.GetAll())
func assertTowersEqual(t *testing.T, expected, actual *Tower) {
t.Helper()
require.True(t, expected.IdentityKey.IsEqual(actual.IdentityKey))
require.Equal(t, expected.ID, actual.ID)
require.Equal(t, expected.Addresses.GetAll(), actual.Addresses.GetAll())
} }
// TestTowerCandidateIterator asserts the internal state of a // TestTowerCandidateIterator asserts the internal state of a
@@ -155,4 +157,16 @@ func TestTowerCandidateIterator(t *testing.T) {
towerIterator.AddCandidate(secondTower) towerIterator.AddCandidate(secondTower)
assertActiveCandidate(t, towerIterator, secondTower, true) assertActiveCandidate(t, towerIterator, secondTower, true)
assertNextCandidate(t, towerIterator, secondTower) assertNextCandidate(t, towerIterator, secondTower)
// Assert that the GetTower correctly returns the tower too.
tower, err := towerIterator.GetTower(secondTower.ID)
require.NoError(t, err)
assertTowersEqual(t, secondTower, tower)
// Now remove the tower and assert that GetTower returns expected error.
err = towerIterator.RemoveCandidate(secondTower.ID, nil)
require.NoError(t, err)
_, err = towerIterator.GetTower(secondTower.ID)
require.ErrorIs(t, err, ErrTowerNotInIterator)
} }
+512 -62
View File
@@ -2,7 +2,10 @@ package wtclient
import ( import (
"bytes" "bytes"
"crypto/rand"
"errors"
"fmt" "fmt"
"math/big"
"net" "net"
"sync" "sync"
"time" "time"
@@ -11,11 +14,14 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btclog" "github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy" "github.com/lightningnetwork/lnd/watchtower/wtpolicy"
@@ -40,14 +46,39 @@ const (
// client should abandon any pending updates or session negotiations // client should abandon any pending updates or session negotiations
// before terminating. // before terminating.
DefaultForceQuitDelay = 10 * time.Second DefaultForceQuitDelay = 10 * time.Second
// DefaultSessionCloseRange is the range over which we will generate a
// random number of blocks to delay closing a session after its last
// channel has been closed.
DefaultSessionCloseRange = 288
) )
// genActiveSessionFilter generates a filter that selects active sessions that // genSessionFilter constructs a filter that can be used to select sessions only
// also match the desired channel type, either legacy or anchor. // if they match the policy of the client (namely anchor vs legacy). If
func genActiveSessionFilter(anchor bool) func(*ClientSession) bool { // activeOnly is set, then only active sessions will be returned.
return func(s *ClientSession) bool { func (c *TowerClient) genSessionFilter(
return s.Status == wtdb.CSessionActive && activeOnly bool) wtdb.ClientSessionFilterFn {
anchor == s.Policy.IsAnchorChannel()
return func(session *wtdb.ClientSession) bool {
if c.cfg.Policy.IsAnchorChannel() !=
session.Policy.IsAnchorChannel() {
return false
}
if !activeOnly {
return true
}
return session.Status == wtdb.CSessionActive
}
}
// ExhaustedSessionFilter constructs a wtdb.ClientSessionFilterFn filter
// function that will filter out any sessions that have been exhausted.
func ExhaustedSessionFilter() wtdb.ClientSessionFilterFn {
return func(session *wtdb.ClientSession) bool {
return session.SeqNum < session.Policy.MaxUpdates
} }
} }
@@ -134,6 +165,19 @@ type Config struct {
// transaction. // transaction.
Signer input.Signer Signer input.Signer
// SubscribeChannelEvents can be used to subscribe to channel event
// notifications.
SubscribeChannelEvents func() (subscribe.Subscription, error)
// FetchClosedChannel can be used to fetch the info about a closed
// channel. If the channel is not found or not yet closed then
// channeldb.ErrClosedChannelNotFound will be returned.
FetchClosedChannel func(cid lnwire.ChannelID) (
*channeldb.ChannelCloseSummary, error)
// ChainNotifier can be used to subscribe to block notifications.
ChainNotifier chainntnfs.ChainNotifier
// NewAddress generates a new on-chain sweep pkscript. // NewAddress generates a new on-chain sweep pkscript.
NewAddress func() ([]byte, error) NewAddress func() ([]byte, error)
@@ -189,6 +233,11 @@ type Config struct {
// watchtowers. If the exponential backoff produces a timeout greater // watchtowers. If the exponential backoff produces a timeout greater
// than this value, the backoff will be clamped to MaxBackoff. // than this value, the backoff will be clamped to MaxBackoff.
MaxBackoff time.Duration MaxBackoff time.Duration
// SessionCloseRange is the range over which we will generate a random
// number of blocks to delay closing a session after its last channel
// has been closed.
SessionCloseRange uint32
} }
// newTowerMsg is an internal message we'll use within the TowerClient to signal // newTowerMsg is an internal message we'll use within the TowerClient to signal
@@ -246,6 +295,8 @@ type TowerClient struct {
sessionQueue *sessionQueue sessionQueue *sessionQueue
prevTask *backupTask prevTask *backupTask
closableSessionQueue *sessionCloseMinHeap
backupMu sync.Mutex backupMu sync.Mutex
summaries wtdb.ChannelSummaries summaries wtdb.ChannelSummaries
chanCommitHeights map[lnwire.ChannelID]uint64 chanCommitHeights map[lnwire.ChannelID]uint64
@@ -257,6 +308,7 @@ type TowerClient struct {
staleTowers chan *staleTowerMsg staleTowers chan *staleTowerMsg
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{}
forceQuit chan struct{} forceQuit chan struct{}
} }
@@ -296,17 +348,19 @@ func New(config *Config) (*TowerClient, error) {
} }
c := &TowerClient{ c := &TowerClient{
cfg: cfg, cfg: cfg,
log: plog, log: plog,
pipeline: newTaskPipeline(plog), pipeline: newTaskPipeline(plog),
chanCommitHeights: make(map[lnwire.ChannelID]uint64), chanCommitHeights: make(map[lnwire.ChannelID]uint64),
activeSessions: make(sessionQueueSet), activeSessions: make(sessionQueueSet),
summaries: chanSummaries, summaries: chanSummaries,
statTicker: time.NewTicker(DefaultStatInterval), closableSessionQueue: newSessionCloseMinHeap(),
stats: new(ClientStats), statTicker: time.NewTicker(DefaultStatInterval),
newTowers: make(chan *newTowerMsg), stats: new(ClientStats),
staleTowers: make(chan *staleTowerMsg), newTowers: make(chan *newTowerMsg),
forceQuit: make(chan struct{}), staleTowers: make(chan *staleTowerMsg),
forceQuit: make(chan struct{}),
quit: make(chan struct{}),
} }
// perUpdate is a callback function that will be used to inspect the // perUpdate is a callback function that will be used to inspect the
@@ -344,13 +398,6 @@ func New(config *Config) (*TowerClient, error) {
perUpdate(s.Policy, u.BackupID.ChanID, u.BackupID.CommitHeight) perUpdate(s.Policy, u.BackupID.ChanID, u.BackupID.CommitHeight)
} }
// Load all candidate sessions and towers from the database into the
// client. We will use any of these sessions if their policies match the
// current policy of the client, otherwise they will be ignored and new
// sessions will be requested.
isAnchorClient := cfg.Policy.IsAnchorChannel()
activeSessionFilter := genActiveSessionFilter(isAnchorClient)
candidateTowers := newTowerListIterator() candidateTowers := newTowerListIterator()
perActiveTower := func(tower *Tower) { perActiveTower := func(tower *Tower) {
// If the tower has already been marked as active, then there is // If the tower has already been marked as active, then there is
@@ -359,17 +406,23 @@ func New(config *Config) (*TowerClient, error) {
return return
} }
log.Infof("Using private watchtower %s, offering policy %s", c.log.Infof("Using private watchtower %x, offering policy %s",
tower, cfg.Policy) tower.IdentityKey.SerializeCompressed(), cfg.Policy)
// Add the tower to the set of candidate towers. // Add the tower to the set of candidate towers.
candidateTowers.AddCandidate(tower) candidateTowers.AddCandidate(tower)
} }
// Load all candidate sessions and towers from the database into the
// client. We will use any of these sessions if their policies match the
// current policy of the client, otherwise they will be ignored and new
// sessions will be requested.
candidateSessions, err := getTowerAndSessionCandidates( candidateSessions, err := getTowerAndSessionCandidates(
cfg.DB, cfg.SecretKeyRing, activeSessionFilter, perActiveTower, cfg.DB, cfg.SecretKeyRing, perActiveTower,
wtdb.WithPreEvalFilterFn(c.genSessionFilter(true)),
wtdb.WithPerMaxHeight(perMaxHeight), wtdb.WithPerMaxHeight(perMaxHeight),
wtdb.WithPerCommittedUpdate(perCommittedUpdate), wtdb.WithPerCommittedUpdate(perCommittedUpdate),
wtdb.WithPostEvalFilterFn(ExhaustedSessionFilter()),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -401,7 +454,6 @@ func New(config *Config) (*TowerClient, error) {
// sessionFilter check then the perActiveTower call-back will be called on that // sessionFilter check then the perActiveTower call-back will be called on that
// tower. // tower.
func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing, func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
sessionFilter func(*ClientSession) bool,
perActiveTower func(tower *Tower), perActiveTower func(tower *Tower),
opts ...wtdb.ClientSessionListOption) ( opts ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*ClientSession, error) { map[wtdb.SessionID]*ClientSession, error) {
@@ -424,33 +476,16 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
} }
for _, s := range sessions { for _, s := range sessions {
towerKeyDesc, err := keyRing.DeriveKey( cs, err := NewClientSessionFromDBSession(
keychain.KeyLocator{ s, tower, keyRing,
Family: keychain.KeyFamilyTowerSession,
Index: s.KeyIndex,
},
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
sessionKeyECDH := keychain.NewPubKeyECDH(
towerKeyDesc, keyRing,
)
cs := &ClientSession{
ID: s.ID,
ClientSessionBody: s.ClientSessionBody,
Tower: tower,
SessionKeyECDH: sessionKeyECDH,
}
if !sessionFilter(cs) {
continue
}
// Add the session to the set of candidate sessions. // Add the session to the set of candidate sessions.
candidateSessions[s.ID] = cs candidateSessions[s.ID] = cs
perActiveTower(tower) perActiveTower(tower)
} }
} }
@@ -466,7 +501,6 @@ func getTowerAndSessionCandidates(db DB, keyRing ECDHKeyRing,
// ClientSession's SessionPrivKey field is desired, otherwise, the existing // ClientSession's SessionPrivKey field is desired, otherwise, the existing
// ListClientSessions method should be used. // ListClientSessions method should be used.
func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID, func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
passesFilter func(*ClientSession) bool,
opts ...wtdb.ClientSessionListOption) ( opts ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*ClientSession, error) { map[wtdb.SessionID]*ClientSession, error) {
@@ -494,6 +528,7 @@ func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
if err != nil { if err != nil {
return nil, err return nil, err
} }
sessionKeyECDH := keychain.NewPubKeyECDH(towerKeyDesc, keyRing) sessionKeyECDH := keychain.NewPubKeyECDH(towerKeyDesc, keyRing)
tower, err := NewTowerFromDBTower(dbTower) tower, err := NewTowerFromDBTower(dbTower)
@@ -501,20 +536,12 @@ func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
return nil, err return nil, err
} }
cs := &ClientSession{ sessions[s.ID] = &ClientSession{
ID: s.ID, ID: s.ID,
ClientSessionBody: s.ClientSessionBody, ClientSessionBody: s.ClientSessionBody,
Tower: tower, Tower: tower,
SessionKeyECDH: sessionKeyECDH, SessionKeyECDH: sessionKeyECDH,
} }
// If an optional filter was provided, use it to filter out any
// undesired sessions.
if passesFilter != nil && !passesFilter(cs) {
continue
}
sessions[s.ID] = cs
} }
return sessions, nil return sessions, nil
@@ -547,10 +574,70 @@ func (c *TowerClient) Start() error {
} }
} }
chanSub, err := c.cfg.SubscribeChannelEvents()
if err != nil {
returnErr = err
return
}
// Iterate over the list of registered channels and check if
// any of them can be marked as closed.
for id := range c.summaries {
isClosed, closedHeight, err := c.isChannelClosed(id)
if err != nil {
returnErr = err
return
}
if !isClosed {
continue
}
_, err = c.cfg.DB.MarkChannelClosed(id, closedHeight)
if err != nil {
c.log.Errorf("could not mark channel(%s) as "+
"closed: %v", id, err)
continue
}
// Since the channel has been marked as closed, we can
// also remove it from the channel summaries map.
delete(c.summaries, id)
}
// Load all closable sessions.
closableSessions, err := c.cfg.DB.ListClosableSessions()
if err != nil {
returnErr = err
return
}
err = c.trackClosableSessions(closableSessions)
if err != nil {
returnErr = err
return
}
c.wg.Add(1)
go c.handleChannelCloses(chanSub)
// Subscribe to new block events.
blockEvents, err := c.cfg.ChainNotifier.RegisterBlockEpochNtfn(
nil,
)
if err != nil {
returnErr = err
return
}
c.wg.Add(1)
go c.handleClosableSessions(blockEvents)
// Now start the session negotiator, which will allow us to // Now start the session negotiator, which will allow us to
// request new session as soon as the backupDispatcher starts // request new session as soon as the backupDispatcher starts
// up. // up.
err := c.negotiator.Start() err = c.negotiator.Start()
if err != nil { if err != nil {
returnErr = err returnErr = err
return return
@@ -598,6 +685,7 @@ func (c *TowerClient) Stop() error {
// dispatcher to exit. The backup queue will signal it's // dispatcher to exit. The backup queue will signal it's
// completion to the dispatcher, which releases the wait group // completion to the dispatcher, which releases the wait group
// after all tasks have been assigned to session queues. // after all tasks have been assigned to session queues.
close(c.quit)
c.wg.Wait() c.wg.Wait()
// 4. Since all valid tasks have been assigned to session // 4. Since all valid tasks have been assigned to session
@@ -779,6 +867,335 @@ func (c *TowerClient) nextSessionQueue() (*sessionQueue, error) {
return c.getOrInitActiveQueue(candidateSession, updates), nil return c.getOrInitActiveQueue(candidateSession, updates), nil
} }
// handleChannelCloses listens for channel close events and marks channels as
// closed in the DB.
//
// NOTE: This method MUST be run as a goroutine.
func (c *TowerClient) handleChannelCloses(chanSub subscribe.Subscription) {
defer c.wg.Done()
c.log.Debugf("Starting channel close handler")
defer c.log.Debugf("Stopping channel close handler")
for {
select {
case update, ok := <-chanSub.Updates():
if !ok {
c.log.Debugf("Channel notifier has exited")
return
}
// We only care about channel-close events.
event, ok := update.(channelnotifier.ClosedChannelEvent)
if !ok {
continue
}
chanID := lnwire.NewChanIDFromOutPoint(
&event.CloseSummary.ChanPoint,
)
c.log.Debugf("Received ClosedChannelEvent for "+
"channel: %s", chanID)
err := c.handleClosedChannel(
chanID, event.CloseSummary.CloseHeight,
)
if err != nil {
c.log.Errorf("Could not handle channel close "+
"event for channel(%s): %v", chanID,
err)
}
case <-c.forceQuit:
return
case <-c.quit:
return
}
}
}
// handleClosedChannel handles the closure of a single channel. It will mark the
// channel as closed in the DB, then it will handle all the sessions that are
// now closable due to the channel closure.
func (c *TowerClient) handleClosedChannel(chanID lnwire.ChannelID,
closeHeight uint32) error {
c.backupMu.Lock()
defer c.backupMu.Unlock()
// We only care about channels registered with the tower client.
if _, ok := c.summaries[chanID]; !ok {
return nil
}
c.log.Debugf("Marking channel(%s) as closed", chanID)
sessions, err := c.cfg.DB.MarkChannelClosed(chanID, closeHeight)
if err != nil {
return fmt.Errorf("could not mark channel(%s) as closed: %w",
chanID, err)
}
closableSessions := make(map[wtdb.SessionID]uint32, len(sessions))
for _, sess := range sessions {
closableSessions[sess] = closeHeight
}
c.log.Debugf("Tracking %d new closable sessions as a result of "+
"closing channel %s", len(closableSessions), chanID)
err = c.trackClosableSessions(closableSessions)
if err != nil {
return fmt.Errorf("could not track closable sessions: %w", err)
}
delete(c.summaries, chanID)
delete(c.chanCommitHeights, chanID)
return nil
}
// handleClosableSessions listens for new block notifications. For each block,
// it checks the closableSessionQueue to see if there is a closable session with
// a delete-height smaller than or equal to the new block, if there is then the
// tower is informed that it can delete the session, and then we also delete it
// from our DB.
func (c *TowerClient) handleClosableSessions(
blocksChan *chainntnfs.BlockEpochEvent) {
defer c.wg.Done()
c.log.Debug("Starting closable sessions handler")
defer c.log.Debug("Stopping closable sessions handler")
for {
select {
case newBlock := <-blocksChan.Epochs:
if newBlock == nil {
return
}
height := uint32(newBlock.Height)
for {
select {
case <-c.quit:
return
default:
}
// If there are no closable sessions that we
// need to handle, then we are done and can
// reevaluate when the next block comes.
item := c.closableSessionQueue.Top()
if item == nil {
break
}
// If there is closable session but the delete
// height we have set for it is after the
// current block height, then our work is done.
if item.deleteHeight > height {
break
}
// Otherwise, we pop this item from the heap
// and handle it.
c.closableSessionQueue.Pop()
// Fetch the session from the DB so that we can
// extract the Tower info.
sess, err := c.cfg.DB.GetClientSession(
item.sessionID,
)
if err != nil {
c.log.Errorf("error calling "+
"GetClientSession for "+
"session %s: %v",
item.sessionID, err)
continue
}
err = c.deleteSessionFromTower(sess)
if err != nil {
c.log.Errorf("error deleting "+
"session %s from tower: %v",
sess.ID, err)
continue
}
err = c.cfg.DB.DeleteSession(item.sessionID)
if err != nil {
c.log.Errorf("could not delete "+
"session(%s) from DB: %w",
sess.ID, err)
continue
}
}
case <-c.forceQuit:
return
case <-c.quit:
return
}
}
}
// trackClosableSessions takes in a map of session IDs to the earliest block
// height at which the session should be deleted. For each of the sessions,
// a random delay is added to the block height and the session is added to the
// closableSessionQueue.
func (c *TowerClient) trackClosableSessions(
sessions map[wtdb.SessionID]uint32) error {
// For each closable session, add a random delay to its close
// height and add it to the closableSessionQueue.
for sID, blockHeight := range sessions {
delay, err := newRandomDelay(c.cfg.SessionCloseRange)
if err != nil {
return err
}
deleteHeight := blockHeight + delay
c.closableSessionQueue.Push(&sessionCloseItem{
sessionID: sID,
deleteHeight: deleteHeight,
})
}
return nil
}
// deleteSessionFromTower dials the tower that we created the session with and
// attempts to send the tower the DeleteSession message.
func (c *TowerClient) deleteSessionFromTower(sess *wtdb.ClientSession) error {
// First, we check if we have already loaded this tower in our
// candidate towers iterator.
tower, err := c.candidateTowers.GetTower(sess.TowerID)
if errors.Is(err, ErrTowerNotInIterator) {
// If not, then we attempt to load it from the DB.
dbTower, err := c.cfg.DB.LoadTowerByID(sess.TowerID)
if err != nil {
return err
}
tower, err = NewTowerFromDBTower(dbTower)
if err != nil {
return err
}
} else if err != nil {
return err
}
session, err := NewClientSessionFromDBSession(
sess, tower, c.cfg.SecretKeyRing,
)
if err != nil {
return err
}
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(wtwire.AltruistSessionsRequired),
c.cfg.ChainHash,
)
var (
conn wtserver.Peer
// addrIterator is a copy of the tower's address iterator.
// We use this copy so that iterating through the addresses does
// not affect any other threads using this iterator.
addrIterator = tower.Addresses.Copy()
towerAddr = addrIterator.Peek()
)
// Attempt to dial the tower with its available addresses.
for {
conn, err = c.dial(
session.SessionKeyECDH, &lnwire.NetAddress{
IdentityKey: tower.IdentityKey,
Address: towerAddr,
},
)
if err != nil {
// If there are more addrs available, immediately try
// those.
nextAddr, iteratorErr := addrIterator.Next()
if iteratorErr == nil {
towerAddr = nextAddr
continue
}
// Otherwise, if we have exhausted the address list,
// exit.
addrIterator.Reset()
return fmt.Errorf("failed to dial tower(%x) at any "+
"available addresses",
tower.IdentityKey.SerializeCompressed())
}
break
}
defer conn.Close()
// Send Init to tower.
err = c.sendMessage(conn, localInit)
if err != nil {
return err
}
// Receive Init from tower.
remoteMsg, err := c.readMessage(conn)
if err != nil {
return err
}
remoteInit, ok := remoteMsg.(*wtwire.Init)
if !ok {
return fmt.Errorf("watchtower %s responded with %T to Init",
towerAddr, remoteMsg)
}
// Validate Init.
err = localInit.CheckRemoteInit(remoteInit, wtwire.FeatureNames)
if err != nil {
return err
}
// Send DeleteSession to tower.
err = c.sendMessage(conn, &wtwire.DeleteSession{})
if err != nil {
return err
}
// Receive DeleteSessionReply from tower.
remoteMsg, err = c.readMessage(conn)
if err != nil {
return err
}
deleteSessionReply, ok := remoteMsg.(*wtwire.DeleteSessionReply)
if !ok {
return fmt.Errorf("watchtower %s responded with %T to "+
"DeleteSession", towerAddr, remoteMsg)
}
switch deleteSessionReply.Code {
case wtwire.CodeOK, wtwire.DeleteSessionCodeNotFound:
return nil
default:
return fmt.Errorf("received error code %v in "+
"DeleteSessionReply when attempting to delete "+
"session from tower", deleteSessionReply.Code)
}
}
// backupDispatcher processes events coming from the taskPipeline and is // backupDispatcher processes events coming from the taskPipeline and is
// responsible for detecting when the client needs to renegotiate a session to // responsible for detecting when the client needs to renegotiate a session to
// fulfill continuing demand. The event loop exits after all tasks have been // fulfill continuing demand. The event loop exits after all tasks have been
@@ -1152,6 +1569,22 @@ func (c *TowerClient) initActiveQueue(s *ClientSession,
return sq return sq
} }
// isChanClosed can be used to check if the channel with the given ID has been
// closed. If it has been, the block height in which its closing transaction was
// mined will also be returned.
func (c *TowerClient) isChannelClosed(id lnwire.ChannelID) (bool, uint32,
error) {
chanSum, err := c.cfg.FetchClosedChannel(id)
if errors.Is(err, channeldb.ErrClosedChannelNotFound) {
return false, 0, nil
} else if err != nil {
return false, 0, err
}
return true, chanSum.CloseHeight, nil
}
// AddTower adds a new watchtower reachable at the given address and considers // AddTower adds a new watchtower reachable at the given address and considers
// it for new sessions. If the watchtower already exists, then any new addresses // it for new sessions. If the watchtower already exists, then any new addresses
// included will be considered when dialing it for session negotiations and // included will be considered when dialing it for session negotiations and
@@ -1200,10 +1633,10 @@ func (c *TowerClient) handleNewTower(msg *newTowerMsg) error {
c.candidateTowers.AddCandidate(tower) c.candidateTowers.AddCandidate(tower)
// Include all of its corresponding sessions to our set of candidates. // Include all of its corresponding sessions to our set of candidates.
isAnchorClient := c.cfg.Policy.IsAnchorChannel()
activeSessionFilter := genActiveSessionFilter(isAnchorClient)
sessions, err := getClientSessions( sessions, err := getClientSessions(
c.cfg.DB, c.cfg.SecretKeyRing, &tower.ID, activeSessionFilter, c.cfg.DB, c.cfg.SecretKeyRing, &tower.ID,
wtdb.WithPreEvalFilterFn(c.genSessionFilter(true)),
wtdb.WithPostEvalFilterFn(ExhaustedSessionFilter()),
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to determine sessions for tower %x: "+ return fmt.Errorf("unable to determine sessions for tower %x: "+
@@ -1320,6 +1753,9 @@ func (c *TowerClient) RegisteredTowers(opts ...wtdb.ClientSessionListOption) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
opts = append(opts, wtdb.WithPreEvalFilterFn(c.genSessionFilter(false)))
clientSessions, err := c.cfg.DB.ListClientSessions(nil, opts...) clientSessions, err := c.cfg.DB.ListClientSessions(nil, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1361,6 +1797,8 @@ func (c *TowerClient) LookupTower(pubKey *btcec.PublicKey,
return nil, err return nil, err
} }
opts = append(opts, wtdb.WithPreEvalFilterFn(c.genSessionFilter(false)))
towerSessions, err := c.cfg.DB.ListClientSessions(&tower.ID, opts...) towerSessions, err := c.cfg.DB.ListClientSessions(&tower.ID, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1405,3 +1843,15 @@ func (c *TowerClient) logMessage(
preposition, peer.RemotePub().SerializeCompressed(), preposition, peer.RemotePub().SerializeCompressed(),
peer.RemoteAddr()) peer.RemoteAddr())
} }
func newRandomDelay(max uint32) (uint32, error) {
var maxDelay big.Int
maxDelay.SetUint64(uint64(max))
randDelay, err := rand.Int(rand.Reader, &maxDelay)
if err != nil {
return 0, err
}
return uint32(randDelay.Uint64()), nil
}
+500 -90
View File
@@ -1,6 +1,7 @@
package wtclient_test package wtclient_test
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@@ -15,12 +16,15 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtclient" "github.com/lightningnetwork/lnd/watchtower/wtclient"
@@ -393,8 +397,15 @@ type testHarness struct {
server *wtserver.Server server *wtserver.Server
net *mockNet net *mockNet
mu sync.Mutex blockEvents *mockBlockSub
channels map[lnwire.ChannelID]*mockChannel height int32
channelEvents *mockSubscription
sendUpdatesOn bool
mu sync.Mutex
channels map[lnwire.ChannelID]*mockChannel
closedChannels map[lnwire.ChannelID]uint32
quit chan struct{} quit chan struct{}
} }
@@ -441,41 +452,63 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
mockNet := newMockNet() mockNet := newMockNet()
clientDB := wtmock.NewClientDB() clientDB := wtmock.NewClientDB()
clientCfg := &wtclient.Config{
Signer: signer,
Dial: mockNet.Dial,
DB: clientDB,
AuthDial: mockNet.AuthDial,
SecretKeyRing: wtmock.NewSecretKeyRing(),
Policy: cfg.policy,
NewAddress: func() ([]byte, error) {
return addrScript, nil
},
ReadTimeout: timeout,
WriteTimeout: timeout,
MinBackoff: time.Millisecond,
MaxBackoff: time.Second,
ForceQuitDelay: 10 * time.Second,
}
h := &testHarness{ h := &testHarness{
t: t, t: t,
cfg: cfg, cfg: cfg,
signer: signer, signer: signer,
capacity: cfg.localBalance + cfg.remoteBalance, capacity: cfg.localBalance + cfg.remoteBalance,
clientDB: clientDB, clientDB: clientDB,
clientCfg: clientCfg, serverAddr: towerAddr,
serverAddr: towerAddr, serverDB: serverDB,
serverDB: serverDB, serverCfg: serverCfg,
serverCfg: serverCfg, net: mockNet,
net: mockNet, blockEvents: newMockBlockSub(t),
channels: make(map[lnwire.ChannelID]*mockChannel), channelEvents: newMockSubscription(t),
quit: make(chan struct{}), channels: make(map[lnwire.ChannelID]*mockChannel),
closedChannels: make(map[lnwire.ChannelID]uint32),
quit: make(chan struct{}),
} }
t.Cleanup(func() { t.Cleanup(func() {
close(h.quit) close(h.quit)
}) })
fetchChannel := func(id lnwire.ChannelID) (
*channeldb.ChannelCloseSummary, error) {
h.mu.Lock()
defer h.mu.Unlock()
height, ok := h.closedChannels[id]
if !ok {
return nil, channeldb.ErrClosedChannelNotFound
}
return &channeldb.ChannelCloseSummary{CloseHeight: height}, nil
}
h.clientCfg = &wtclient.Config{
Signer: signer,
SubscribeChannelEvents: func() (subscribe.Subscription, error) {
return h.channelEvents, nil
},
FetchClosedChannel: fetchChannel,
ChainNotifier: h.blockEvents,
Dial: mockNet.Dial,
DB: clientDB,
AuthDial: mockNet.AuthDial,
SecretKeyRing: wtmock.NewSecretKeyRing(),
Policy: cfg.policy,
NewAddress: func() ([]byte, error) {
return addrScript, nil
},
ReadTimeout: timeout,
WriteTimeout: timeout,
MinBackoff: time.Millisecond,
MaxBackoff: time.Second,
ForceQuitDelay: 10 * time.Second,
SessionCloseRange: 1,
}
if !cfg.noServerStart { if !cfg.noServerStart {
h.startServer() h.startServer()
t.Cleanup(h.stopServer) t.Cleanup(h.stopServer)
@@ -492,6 +525,16 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
return h return h
} }
// mine mimics the mining of new blocks by sending new block notifications.
func (h *testHarness) mine(numBlocks int) {
h.t.Helper()
for i := 0; i < numBlocks; i++ {
h.height++
h.blockEvents.sendNewBlock(h.height)
}
}
// startServer creates a new server using the harness's current serverCfg and // startServer creates a new server using the harness's current serverCfg and
// starts it after pointing the mockNet's callback to the new server. // starts it after pointing the mockNet's callback to the new server.
func (h *testHarness) startServer() { func (h *testHarness) startServer() {
@@ -576,6 +619,41 @@ func (h *testHarness) channel(id uint64) *mockChannel {
return c return c
} }
// closeChannel marks a channel as closed.
//
// NOTE: The method fails if a channel for id does not exist.
func (h *testHarness) closeChannel(id uint64, height uint32) {
h.t.Helper()
h.mu.Lock()
defer h.mu.Unlock()
chanID := chanIDFromInt(id)
_, ok := h.channels[chanID]
require.Truef(h.t, ok, "unable to fetch channel %d", id)
h.closedChannels[chanID] = height
delete(h.channels, chanID)
chanPointHash, err := chainhash.NewHash(chanID[:])
require.NoError(h.t, err)
if !h.sendUpdatesOn {
return
}
h.channelEvents.sendUpdate(channelnotifier.ClosedChannelEvent{
CloseSummary: &channeldb.ChannelCloseSummary{
ChanPoint: wire.OutPoint{
Hash: *chanPointHash,
Index: 0,
},
CloseHeight: height,
},
})
}
// registerChannel registers the channel identified by id with the client. // registerChannel registers the channel identified by id with the client.
func (h *testHarness) registerChannel(id uint64) { func (h *testHarness) registerChannel(id uint64) {
h.t.Helper() h.t.Helper()
@@ -624,7 +702,7 @@ func (h *testHarness) backupState(id, i uint64, expErr error) {
err := h.client.BackupState( err := h.client.BackupState(
&chanID, retribution, channeldb.SingleFunderBit, &chanID, retribution, channeldb.SingleFunderBit,
) )
require.ErrorIs(h.t, expErr, err) require.ErrorIs(h.t, err, expErr)
} }
// sendPayments instructs the channel identified by id to send amt to the remote // sendPayments instructs the channel identified by id to send amt to the remote
@@ -770,11 +848,132 @@ func (h *testHarness) removeTower(pubKey *btcec.PublicKey, addr net.Addr) {
require.NoError(h.t, err) require.NoError(h.t, err)
} }
// relevantSessions returns a list of session IDs that have acked updates for
// the given channel ID.
func (h *testHarness) relevantSessions(chanID uint64) []wtdb.SessionID {
h.t.Helper()
var (
sessionIDs []wtdb.SessionID
cID = chanIDFromInt(chanID)
)
collectSessions := wtdb.WithPerNumAckedUpdates(
func(session *wtdb.ClientSession, id lnwire.ChannelID,
_ uint16) {
if !bytes.Equal(id[:], cID[:]) {
return
}
sessionIDs = append(sessionIDs, session.ID)
},
)
_, err := h.clientDB.ListClientSessions(nil, collectSessions)
require.NoError(h.t, err)
return sessionIDs
}
// isSessionClosable returns true if the given session has been marked as
// closable in the DB.
func (h *testHarness) isSessionClosable(id wtdb.SessionID) bool {
h.t.Helper()
cs, err := h.clientDB.ListClosableSessions()
require.NoError(h.t, err)
_, ok := cs[id]
return ok
}
// mockSubscription is a mock subscription client that blocks on sends into the
// updates channel.
type mockSubscription struct {
t *testing.T
updates chan interface{}
// Embed the subscription interface in this mock so that we satisfy it.
subscribe.Subscription
}
// newMockSubscription creates a mock subscription.
func newMockSubscription(t *testing.T) *mockSubscription {
t.Helper()
return &mockSubscription{
t: t,
updates: make(chan interface{}),
}
}
// sendUpdate sends an update into our updates channel, mocking the dispatch of
// an update from a subscription server. This call will fail the test if the
// update is not consumed within our timeout.
func (m *mockSubscription) sendUpdate(update interface{}) {
select {
case m.updates <- update:
case <-time.After(waitTime):
m.t.Fatalf("update: %v timeout", update)
}
}
// Updates returns the updates channel for the mock.
func (m *mockSubscription) Updates() <-chan interface{} {
return m.updates
}
// mockBlockSub mocks out the ChainNotifier.
type mockBlockSub struct {
t *testing.T
events chan *chainntnfs.BlockEpoch
chainntnfs.ChainNotifier
}
// newMockBlockSub creates a new mockBlockSub.
func newMockBlockSub(t *testing.T) *mockBlockSub {
t.Helper()
return &mockBlockSub{
t: t,
events: make(chan *chainntnfs.BlockEpoch),
}
}
// RegisterBlockEpochNtfn returns a channel that can be used to listen for new
// blocks.
func (m *mockBlockSub) RegisterBlockEpochNtfn(_ *chainntnfs.BlockEpoch) (
*chainntnfs.BlockEpochEvent, error) {
return &chainntnfs.BlockEpochEvent{
Epochs: m.events,
}, nil
}
// sendNewBlock will send a new block on the notification channel.
func (m *mockBlockSub) sendNewBlock(height int32) {
select {
case m.events <- &chainntnfs.BlockEpoch{Height: height}:
case <-time.After(waitTime):
m.t.Fatalf("timed out sending block: %d", height)
}
}
const ( const (
localBalance = lnwire.MilliSatoshi(100000000) localBalance = lnwire.MilliSatoshi(100000000)
remoteBalance = lnwire.MilliSatoshi(200000000) remoteBalance = lnwire.MilliSatoshi(200000000)
) )
var defaultTxPolicy = wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
}
type clientTest struct { type clientTest struct {
name string name string
cfg harnessCfg cfg harnessCfg
@@ -791,10 +990,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 20000, MaxUpdates: 20000,
}, },
noRegisterChan0: true, noRegisterChan0: true,
@@ -825,10 +1021,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 20000, MaxUpdates: 20000,
}, },
}, },
@@ -860,10 +1053,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -927,10 +1117,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 20000, MaxUpdates: 20000,
}, },
}, },
@@ -1006,10 +1193,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -1062,10 +1246,7 @@ var clientTests = []clientTest{
localBalance: 100000001, // ensure (% amt != 0) localBalance: 100000001, // ensure (% amt != 0)
remoteBalance: 200000001, // ensure (% amt != 0) remoteBalance: 200000001, // ensure (% amt != 0)
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 1000, MaxUpdates: 1000,
}, },
}, },
@@ -1106,10 +1287,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -1156,10 +1334,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
noAckCreateSession: true, noAckCreateSession: true,
@@ -1212,10 +1387,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
noAckCreateSession: true, noAckCreateSession: true,
@@ -1274,10 +1446,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 10, MaxUpdates: 10,
}, },
}, },
@@ -1297,7 +1466,7 @@ var clientTests = []clientTest{
h.waitServerUpdates(hints[:numUpdates/2], time.Second) h.waitServerUpdates(hints[:numUpdates/2], time.Second)
// Stop the client, which should have no more backups. // Stop the client, which should have no more backups.
h.client.Stop() require.NoError(h.t, h.client.Stop())
// Record the policy that the first half was stored // Record the policy that the first half was stored
// under. We'll expect the second half to also be stored // under. We'll expect the second half to also be stored
@@ -1333,10 +1502,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -1361,7 +1527,7 @@ var clientTests = []clientTest{
// Restart the client, so we can ensure the deduping is // Restart the client, so we can ensure the deduping is
// maintained across restarts. // maintained across restarts.
h.client.Stop() require.NoError(h.t, h.client.Stop())
h.startClient() h.startClient()
// Try to back up the full range of retributions. Only // Try to back up the full range of retributions. Only
@@ -1381,10 +1547,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -1489,10 +1652,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
}, },
@@ -1557,10 +1717,7 @@ var clientTests = []clientTest{
localBalance: localBalance, localBalance: localBalance,
remoteBalance: remoteBalance, remoteBalance: remoteBalance,
policy: wtpolicy.Policy{ policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{ TxPolicy: defaultTxPolicy,
BlobType: blob.TypeAltruistCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 5, MaxUpdates: 5,
}, },
noServerStart: true, noServerStart: true,
@@ -1654,6 +1811,259 @@ var clientTests = []clientTest{
}, waitTime) }, waitTime)
require.NoError(h.t, err) require.NoError(h.t, err)
}, },
}, {
name: "assert that sessions are correctly marked as closable",
cfg: harnessCfg{
localBalance: localBalance,
remoteBalance: remoteBalance,
policy: wtpolicy.Policy{
TxPolicy: defaultTxPolicy,
MaxUpdates: 5,
},
},
fn: func(h *testHarness) {
const numUpdates = 5
// In this test we assert that a channel is correctly
// marked as closed and that sessions are also correctly
// marked as closable.
// We start with the sendUpdatesOn parameter set to
// false so that we can test that channels are correctly
// evaluated at startup.
h.sendUpdatesOn = false
// Advance channel 0 to create all states and back them
// all up. This will saturate the session with updates
// for channel 0 which means that the session should be
// considered closable when channel 0 is closed.
hints := h.advanceChannelN(0, numUpdates)
h.backupStates(0, 0, numUpdates, nil)
h.waitServerUpdates(hints, waitTime)
// We expect only 1 session to have updates for this
// channel.
sessionIDs := h.relevantSessions(0)
require.Len(h.t, sessionIDs, 1)
// Since channel 0 is still open, the session should not
// yet be closable.
require.False(h.t, h.isSessionClosable(sessionIDs[0]))
// Close the channel.
h.closeChannel(0, 1)
// Since updates are currently not being sent, we expect
// the session to still not be marked as closable.
require.False(h.t, h.isSessionClosable(sessionIDs[0]))
// Restart the client.
h.client.ForceQuit()
h.startClient()
// The session should now have been marked as closable.
err := wait.Predicate(func() bool {
return h.isSessionClosable(sessionIDs[0])
}, waitTime)
require.NoError(h.t, err)
// Now we set sendUpdatesOn to true and do the same with
// a new channel. A restart should now not be necessary
// anymore.
h.sendUpdatesOn = true
h.makeChannel(
1, h.cfg.localBalance, h.cfg.remoteBalance,
)
h.registerChannel(1)
hints = h.advanceChannelN(1, numUpdates)
h.backupStates(1, 0, numUpdates, nil)
h.waitServerUpdates(hints, waitTime)
// Determine the ID of the session of interest.
sessionIDs = h.relevantSessions(1)
// We expect only 1 session to have updates for this
// channel.
require.Len(h.t, sessionIDs, 1)
// Assert that the session is not yet closable since
// the channel is still open.
require.False(h.t, h.isSessionClosable(sessionIDs[0]))
// Now close the channel.
h.closeChannel(1, 1)
// Since the updates have been turned on, the session
// should now show up as closable.
err = wait.Predicate(func() bool {
return h.isSessionClosable(sessionIDs[0])
}, waitTime)
require.NoError(h.t, err)
// Now we test that a session must be exhausted with all
// channels closed before it is seen as closable.
h.makeChannel(
2, h.cfg.localBalance, h.cfg.remoteBalance,
)
h.registerChannel(2)
// Fill up only half of the session updates.
hints = h.advanceChannelN(2, numUpdates)
h.backupStates(2, 0, numUpdates/2, nil)
h.waitServerUpdates(hints[:numUpdates/2], waitTime)
// Determine the ID of the session of interest.
sessionIDs = h.relevantSessions(2)
// We expect only 1 session to have updates for this
// channel.
require.Len(h.t, sessionIDs, 1)
// Now close the channel.
h.closeChannel(2, 1)
// The session should _not_ be closable due to it not
// being exhausted yet.
require.False(h.t, h.isSessionClosable(sessionIDs[0]))
// Create a new channel.
h.makeChannel(
3, h.cfg.localBalance, h.cfg.remoteBalance,
)
h.registerChannel(3)
hints = h.advanceChannelN(3, numUpdates)
h.backupStates(3, 0, numUpdates, nil)
h.waitServerUpdates(hints, waitTime)
// Close it.
h.closeChannel(3, 1)
// Now the session should be closable.
err = wait.Predicate(func() bool {
return h.isSessionClosable(sessionIDs[0])
}, waitTime)
require.NoError(h.t, err)
// Now we will mine a few blocks. This will cause the
// necessary session-close-range to be exceeded meaning
// that the client should send the DeleteSession message
// to the server. We will assert that both the client
// and server have deleted the appropriate sessions and
// channel info.
// Before we mine blocks, assert that the client
// currently has 3 closable sessions.
closableSess, err := h.clientDB.ListClosableSessions()
require.NoError(h.t, err)
require.Len(h.t, closableSess, 3)
// Assert that the server is also aware of all of these
// sessions.
for sid := range closableSess {
_, err := h.serverDB.GetSessionInfo(&sid)
require.NoError(h.t, err)
}
// Also make a note of the total number of sessions the
// client has.
sessions, err := h.clientDB.ListClientSessions(nil)
require.NoError(h.t, err)
require.Len(h.t, sessions, 4)
h.mine(3)
// The client should no longer have any closable
// sessions and the total list of client sessions should
// no longer include the three that it previously had
// marked as closable. The server should also no longer
// have these sessions in its DB.
err = wait.Predicate(func() bool {
sess, err := h.clientDB.ListClientSessions(nil)
require.NoError(h.t, err)
cs, err := h.clientDB.ListClosableSessions()
require.NoError(h.t, err)
if len(sess) != 1 || len(cs) != 0 {
return false
}
for sid := range closableSess {
_, ok := sess[sid]
if ok {
return false
}
_, err := h.serverDB.GetSessionInfo(
&sid,
)
if !errors.Is(
err, wtdb.ErrSessionNotFound,
) {
return false
}
}
return true
}, waitTime)
require.NoError(h.t, err)
},
},
{
// Demonstrate that the client is unable to recover after
// deleting its database by skipping through key indices until
// it gets to one that does not result in the
// CreateSessionCodeAlreadyExists error code being returned from
// the server.
name: "continue after client database deletion",
cfg: harnessCfg{
localBalance: localBalance,
remoteBalance: remoteBalance,
policy: wtpolicy.Policy{
TxPolicy: defaultTxPolicy,
MaxUpdates: 5,
},
},
fn: func(h *testHarness) {
const (
numUpdates = 5
chanID = 0
)
// Generate numUpdates retributions.
hints := h.advanceChannelN(chanID, numUpdates)
// Back half of the states up.
h.backupStates(chanID, 0, numUpdates/2, nil)
// Wait for the updates to be populated in the server's
// database.
h.waitServerUpdates(hints[:numUpdates/2], waitTime)
// Now stop the client and reset its database.
require.NoError(h.t, h.client.Stop())
db := wtmock.NewClientDB()
h.clientDB = db
h.clientCfg.DB = db
// Restart the client.
h.startClient()
// We need to re-register the channel due to the client
// db being reset.
h.registerChannel(0)
// Attempt to back up the remaining tasks.
h.backupStates(chanID, numUpdates/2, numUpdates, nil)
// Show that the server does get the remaining updates.
h.waitServerUpdates(hints[numUpdates/2:], waitTime)
},
}, },
} }
+12 -1
View File
@@ -1,6 +1,8 @@
package wtclient package wtclient
import "errors" import (
"errors"
)
var ( var (
// ErrClientExiting signals that the watchtower client is shutting down. // ErrClientExiting signals that the watchtower client is shutting down.
@@ -11,6 +13,10 @@ var (
ErrTowerCandidatesExhausted = errors.New("exhausted all tower " + ErrTowerCandidatesExhausted = errors.New("exhausted all tower " +
"candidates") "candidates")
// ErrTowerNotInIterator is returned when a requested tower was not
// found in the iterator.
ErrTowerNotInIterator = errors.New("tower not in iterator")
// ErrPermanentTowerFailure signals that the tower has reported that it // ErrPermanentTowerFailure signals that the tower has reported that it
// has permanently failed or the client believes this has happened based // has permanently failed or the client believes this has happened based
// on the tower's behavior. // on the tower's behavior.
@@ -28,4 +34,9 @@ var (
// revoked state because the channel had not been previously registered // revoked state because the channel had not been previously registered
// with the client. // with the client.
ErrUnregisteredChannel = errors.New("channel is not registered") ErrUnregisteredChannel = errors.New("channel is not registered")
// ErrSessionKeyAlreadyUsed indicates that the client attempted to
// create a new session with a tower with a session key that has already
// been used in the past.
ErrSessionKeyAlreadyUsed = errors.New("session key already used")
) )
+55 -3
View File
@@ -49,8 +49,8 @@ type DB interface {
// (tower, blob type) pair until CreateClientSession is invoked for that // (tower, blob type) pair until CreateClientSession is invoked for that
// tower and index, at which point a new index for that tower can be // tower and index, at which point a new index for that tower can be
// reserved. Multiple calls to this method before CreateClientSession is // reserved. Multiple calls to this method before CreateClientSession is
// invoked should return the same index. // invoked should return the same index unless forceNext is true.
NextSessionKeyIndex(wtdb.TowerID, blob.Type) (uint32, error) NextSessionKeyIndex(wtdb.TowerID, blob.Type, bool) (uint32, error)
// CreateClientSession saves a newly negotiated client session to the // CreateClientSession saves a newly negotiated client session to the
// client's database. This enables the session to be used across // client's database. This enables the session to be used across
@@ -63,6 +63,11 @@ type DB interface {
ListClientSessions(*wtdb.TowerID, ...wtdb.ClientSessionListOption) ( ListClientSessions(*wtdb.TowerID, ...wtdb.ClientSessionListOption) (
map[wtdb.SessionID]*wtdb.ClientSession, error) map[wtdb.SessionID]*wtdb.ClientSession, error)
// GetClientSession loads the ClientSession with the given ID from the
// DB.
GetClientSession(wtdb.SessionID,
...wtdb.ClientSessionListOption) (*wtdb.ClientSession, error)
// FetchSessionCommittedUpdates retrieves the current set of un-acked // FetchSessionCommittedUpdates retrieves the current set of un-acked
// updates of the given session. // updates of the given session.
FetchSessionCommittedUpdates(id *wtdb.SessionID) ( FetchSessionCommittedUpdates(id *wtdb.SessionID) (
@@ -77,9 +82,29 @@ type DB interface {
NumAckedUpdates(id *wtdb.SessionID) (uint64, error) NumAckedUpdates(id *wtdb.SessionID) (uint64, error)
// FetchChanSummaries loads a mapping from all registered channels to // FetchChanSummaries loads a mapping from all registered channels to
// their channel summaries. // their channel summaries. Only the channels that have not yet been
// marked as closed will be loaded.
FetchChanSummaries() (wtdb.ChannelSummaries, error) FetchChanSummaries() (wtdb.ChannelSummaries, error)
// MarkChannelClosed will mark a registered channel as closed by setting
// its closed-height as the given block height. It returns a list of
// session IDs for sessions that are now considered closable due to the
// close of this channel. The details for this channel will be deleted
// from the DB if there are no more sessions in the DB that contain
// updates for this channel.
MarkChannelClosed(chanID lnwire.ChannelID, blockHeight uint32) (
[]wtdb.SessionID, error)
// ListClosableSessions fetches and returns the IDs for all sessions
// marked as closable.
ListClosableSessions() (map[wtdb.SessionID]uint32, error)
// DeleteSession can be called when a session should be deleted from the
// DB. All references to the session will also be deleted from the DB.
// A session will only be deleted if it was previously marked as
// closable.
DeleteSession(id wtdb.SessionID) error
// RegisterChannel registers a channel for use within the client // RegisterChannel registers a channel for use within the client
// database. For now, all that is stored in the channel summary is the // database. For now, all that is stored in the channel summary is the
// sweep pkscript that we'd like any tower sweeps to pay into. In the // sweep pkscript that we'd like any tower sweeps to pay into. In the
@@ -173,3 +198,30 @@ type ClientSession struct {
// key used to connect to the watchtower. // key used to connect to the watchtower.
SessionKeyECDH keychain.SingleKeyECDH SessionKeyECDH keychain.SingleKeyECDH
} }
// NewClientSessionFromDBSession converts a wtdb.ClientSession to a
// ClientSession.
func NewClientSessionFromDBSession(s *wtdb.ClientSession, tower *Tower,
keyRing ECDHKeyRing) (*ClientSession, error) {
towerKeyDesc, err := keyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: s.KeyIndex,
},
)
if err != nil {
return nil, err
}
sessionKeyECDH := keychain.NewPubKeyECDH(
towerKeyDesc, keyRing,
)
return &ClientSession{
ID: s.ID,
ClientSessionBody: s.ClientSessionBody,
Tower: tower,
SessionKeyECDH: sessionKeyECDH,
}, nil
}
@@ -0,0 +1,95 @@
package wtclient
import (
"sync"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
)
// sessionCloseMinHeap is a thread-safe min-heap implementation that stores
// sessionCloseItem items and prioritises the item with the lowest block height.
type sessionCloseMinHeap struct {
queue queue.PriorityQueue
mu sync.Mutex
}
// newSessionCloseMinHeap constructs a new sessionCloseMineHeap.
func newSessionCloseMinHeap() *sessionCloseMinHeap {
return &sessionCloseMinHeap{}
}
// Len returns the length of the queue.
func (h *sessionCloseMinHeap) Len() int {
h.mu.Lock()
defer h.mu.Unlock()
return h.queue.Len()
}
// Empty returns true if the queue is empty.
func (h *sessionCloseMinHeap) Empty() bool {
h.mu.Lock()
defer h.mu.Unlock()
return h.queue.Empty()
}
// Push adds an item to the priority queue.
func (h *sessionCloseMinHeap) Push(item *sessionCloseItem) {
h.mu.Lock()
defer h.mu.Unlock()
h.queue.Push(item)
}
// Pop removes the top most item from the queue.
func (h *sessionCloseMinHeap) Pop() *sessionCloseItem {
h.mu.Lock()
defer h.mu.Unlock()
if h.queue.Empty() {
return nil
}
item := h.queue.Pop()
return item.(*sessionCloseItem) //nolint:forcetypeassert
}
// Top returns the top most item from the queue without removing it.
func (h *sessionCloseMinHeap) Top() *sessionCloseItem {
h.mu.Lock()
defer h.mu.Unlock()
if h.queue.Empty() {
return nil
}
item := h.queue.Top()
return item.(*sessionCloseItem) //nolint:forcetypeassert
}
// sessionCloseItem represents a session that is ready to be deleted.
type sessionCloseItem struct {
// sessionID is the ID of the session in question.
sessionID wtdb.SessionID
// deleteHeight is the block height after which we can delete the
// session.
deleteHeight uint32
}
// Less returns true if the current item's delete height is less than the
// other sessionCloseItem's delete height. This results in lower block heights
// being popped first from the heap.
//
// NOTE: this is part of the queue.PriorityQueueItem interface.
func (s *sessionCloseItem) Less(other queue.PriorityQueueItem) bool {
o := other.(*sessionCloseItem).deleteHeight //nolint:forcetypeassert
return s.deleteHeight < o
}
var _ queue.PriorityQueueItem = (*sessionCloseItem)(nil)
@@ -0,0 +1,52 @@
package wtclient
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestSessionCloseMinHeap asserts that the sessionCloseMinHeap behaves as
// expected.
func TestSessionCloseMinHeap(t *testing.T) {
t.Parallel()
heap := newSessionCloseMinHeap()
require.Nil(t, heap.Pop())
require.Nil(t, heap.Top())
require.True(t, heap.Empty())
require.Zero(t, heap.Len())
// Add an item with height 10.
item1 := &sessionCloseItem{
sessionID: [33]byte{1, 2, 3},
deleteHeight: 10,
}
heap.Push(item1)
require.Equal(t, item1, heap.Top())
require.False(t, heap.Empty())
require.EqualValues(t, 1, heap.Len())
// Add a bunch more items with heights 1, 2, 6, 11, 6, 30, 9.
heap.Push(&sessionCloseItem{deleteHeight: 1})
heap.Push(&sessionCloseItem{deleteHeight: 2})
heap.Push(&sessionCloseItem{deleteHeight: 6})
heap.Push(&sessionCloseItem{deleteHeight: 11})
heap.Push(&sessionCloseItem{deleteHeight: 6})
heap.Push(&sessionCloseItem{deleteHeight: 30})
heap.Push(&sessionCloseItem{deleteHeight: 9})
// Now pop from the queue and assert that the items are returned in
// ascending order.
require.EqualValues(t, 1, heap.Pop().deleteHeight)
require.EqualValues(t, 2, heap.Pop().deleteHeight)
require.EqualValues(t, 6, heap.Pop().deleteHeight)
require.EqualValues(t, 6, heap.Pop().deleteHeight)
require.EqualValues(t, 9, heap.Pop().deleteHeight)
require.EqualValues(t, 10, heap.Pop().deleteHeight)
require.EqualValues(t, 11, heap.Pop().deleteHeight)
require.EqualValues(t, 30, heap.Pop().deleteHeight)
require.Nil(t, heap.Pop())
require.Zero(t, heap.Len())
}
+46 -25
View File
@@ -1,6 +1,7 @@
package wtclient package wtclient
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
@@ -272,6 +273,7 @@ retryWithBackoff:
} }
} }
tryNextCandidate:
for { for {
select { select {
case <-n.quit: case <-n.quit:
@@ -302,28 +304,39 @@ retryWithBackoff:
n.log.Debugf("Attempting session negotiation with tower=%x", n.log.Debugf("Attempting session negotiation with tower=%x",
towerPub) towerPub)
// Before proceeding, we will reserve a session key index to use var forceNextKey bool
// with this specific tower. If one is already reserved, the for {
// existing index will be returned. // Before proceeding, we will reserve a session key
keyIndex, err := n.cfg.DB.NextSessionKeyIndex( // index to use with this specific tower. If one is
tower.ID, n.cfg.Policy.BlobType, // already reserved, the existing index will be
) // returned.
if err != nil { keyIndex, err := n.cfg.DB.NextSessionKeyIndex(
n.log.Debugf("Unable to reserve session key index "+ tower.ID, n.cfg.Policy.BlobType, forceNextKey,
"for tower=%x: %v", towerPub, err) )
continue if err != nil {
} n.log.Debugf("Unable to reserve session key "+
"index for tower=%x: %v", towerPub, err)
// We'll now attempt the CreateSession dance with the tower to goto tryNextCandidate
// get a new session, trying all addresses if necessary. }
err = n.createSession(tower, keyIndex)
if err != nil { // We'll now attempt the CreateSession dance with the
// An unexpected error occurred, updpate our backoff. // tower to get a new session, trying all addresses if
// necessary.
err = n.createSession(tower, keyIndex)
if err == nil {
return
} else if errors.Is(err, ErrSessionKeyAlreadyUsed) {
forceNextKey = true
continue
}
// An unexpected error occurred, update our backoff.
updateBackoff() updateBackoff()
n.log.Debugf("Session negotiation with tower=%x "+ n.log.Debugf("Session negotiation with tower=%x "+
"failed, trying again -- reason: %v", "failed, trying again -- reason: %v", towerPub,
tower.IdentityKey.SerializeCompressed(), err) err)
goto retryWithBackoff goto retryWithBackoff
} }
@@ -360,7 +373,10 @@ func (n *sessionNegotiator) createSession(tower *Tower, keyIndex uint32) error {
err = n.tryAddress(sessionKey, keyIndex, tower, lnAddr) err = n.tryAddress(sessionKey, keyIndex, tower, lnAddr)
tower.Addresses.ReleaseLock(addr) tower.Addresses.ReleaseLock(addr)
switch { switch {
case err == ErrPermanentTowerFailure: case errors.Is(err, ErrSessionKeyAlreadyUsed):
return err
case errors.Is(err, ErrPermanentTowerFailure):
// TODO(conner): report to iterator? can then be reset // TODO(conner): report to iterator? can then be reset
// with restart // with restart
fallthrough fallthrough
@@ -454,12 +470,7 @@ func (n *sessionNegotiator) tryAddress(sessionKey keychain.SingleKeyECDH,
} }
switch createSessionReply.Code { switch createSessionReply.Code {
case wtwire.CodeOK, wtwire.CreateSessionCodeAlreadyExists: case wtwire.CodeOK:
// TODO(conner): add last-applied to create session reply to
// handle case where we lose state, session already exists, and
// we want to possibly resume using the session
// TODO(conner): validate reward address // TODO(conner): validate reward address
rewardPkScript := createSessionReply.Data rewardPkScript := createSessionReply.Data
@@ -500,6 +511,16 @@ func (n *sessionNegotiator) tryAddress(sessionKey keychain.SingleKeyECDH,
return ErrNegotiatorExiting return ErrNegotiatorExiting
} }
case wtwire.CreateSessionCodeAlreadyExists:
// TODO(conner): use the last-applied in the create session
// reply to handle case where we lose state, session already
// exists, and we want to possibly resume using the session.
// NOTE that this should not be done until the server code
// has been adapted to first check that the CreateSession
// request is for the same blob-type as the initial session.
return ErrSessionKeyAlreadyUsed
// TODO(conner): handle error codes properly // TODO(conner): handle error codes properly
case wtwire.CreateSessionCodeRejectBlobType: case wtwire.CreateSessionCodeRejectBlobType:
return fmt.Errorf("tower rejected blob type: %v", return fmt.Errorf("tower rejected blob type: %v",
+735 -32
View File
@@ -23,22 +23,39 @@ var (
// cChanDetailsBkt is a top-level bucket storing: // cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary. // channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id // => cChanDBID -> db-assigned-id
// => cChanSessions => db-session-id -> 1
// => cChanClosedHeight -> block-height
cChanDetailsBkt = []byte("client-channel-detail-bucket") cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChanSessions is a sub-bucket of cChanDetailsBkt which stores:
// db-session-id -> 1
cChanSessions = []byte("client-channel-sessions")
// cChanDBID is a key used in the cChanDetailsBkt to store the // cChanDBID is a key used in the cChanDetailsBkt to store the
// db-assigned-id of a channel. // db-assigned-id of a channel.
cChanDBID = []byte("client-channel-db-id") cChanDBID = []byte("client-channel-db-id")
// cChanClosedHeight is a key used in the cChanDetailsBkt to store the
// block height at which the channel's closing transaction was mined in.
// If this there is no associated value for this key, then the channel
// has not yet been marked as closed.
cChanClosedHeight = []byte("client-channel-closed-height")
// cChannelSummary is a key used in cChanDetailsBkt to store the encoded // cChannelSummary is a key used in cChanDetailsBkt to store the encoded
// body of ClientChanSummary. // body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary") cChannelSummary = []byte("client-channel-summary")
// cSessionBkt is a top-level bucket storing: // cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody // session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate // => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAckRangeIndex => db-chan-id => start -> end // => cSessionAckRangeIndex => db-chan-id => start -> end
cSessionBkt = []byte("client-session-bucket") cSessionBkt = []byte("client-session-bucket")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-id of a session.
cSessionDBID = []byte("client-session-db-id")
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of // cSessionBody is a sub-bucket of cSessionBkt storing only the body of
// the ClientSession. // the ClientSession.
cSessionBody = []byte("client-session-body") cSessionBody = []byte("client-session-body")
@@ -55,6 +72,10 @@ var (
// db-assigned-id -> channel-ID // db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index") cChanIDIndexBkt = []byte("client-channel-id-index")
// cSessionIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> session-id
cSessionIDIndexBkt = []byte("client-session-id-index")
// cTowerBkt is a top-level bucket storing: // cTowerBkt is a top-level bucket storing:
// tower-id -> encoded Tower. // tower-id -> encoded Tower.
cTowerBkt = []byte("client-tower-bucket") cTowerBkt = []byte("client-tower-bucket")
@@ -69,6 +90,10 @@ var (
"client-tower-to-session-index-bucket", "client-tower-to-session-index-bucket",
) )
// cClosableSessionsBkt is a top-level bucket storing:
// db-session-id -> last-channel-close-height
cClosableSessionsBkt = []byte("client-closable-sessions-bucket")
// ErrTowerNotFound signals that the target tower was not found in the // ErrTowerNotFound signals that the target tower was not found in the
// database. // database.
ErrTowerNotFound = errors.New("tower not found") ErrTowerNotFound = errors.New("tower not found")
@@ -138,6 +163,27 @@ var (
// range-index found for the given session ID to channel ID pair. // range-index found for the given session ID to channel ID pair.
ErrNoRangeIndexFound = errors.New("no range index found for the " + ErrNoRangeIndexFound = errors.New("no range index found for the " +
"given session-channel pair") "given session-channel pair")
// ErrSessionFailedFilterFn indicates that a particular session did
// not pass the filter func provided by the caller.
ErrSessionFailedFilterFn = errors.New("session failed filter func")
// ErrSessionNotClosable is returned when a session is not found in the
// closable list.
ErrSessionNotClosable = errors.New("session is not closable")
// errSessionHasOpenChannels is an error used to indicate that a
// session has updates for channels that are still open.
errSessionHasOpenChannels = errors.New("session has open channels")
// errSessionHasUnackedUpdates is an error used to indicate that a
// session has un-acked updates.
errSessionHasUnackedUpdates = errors.New("session has un-acked updates")
// errChannelHasMoreSessions is an error used to indicate that a channel
// has updates in other non-closed sessions.
errChannelHasMoreSessions = errors.New("channel has updates in " +
"other sessions")
) )
// NewBoltBackendCreator returns a function that creates a new bbolt backend for // NewBoltBackendCreator returns a function that creates a new bbolt backend for
@@ -237,6 +283,8 @@ func initClientDBBuckets(tx kvdb.RwTx) error {
cTowerIndexBkt, cTowerIndexBkt,
cTowerToSessionIndexBkt, cTowerToSessionIndexBkt,
cChanIDIndexBkt, cChanIDIndexBkt,
cSessionIDIndexBkt,
cClosableSessionsBkt,
} }
for _, bucket := range buckets { for _, bucket := range buckets {
@@ -595,9 +643,10 @@ func (c *ClientDB) ListTowers() ([]*Tower, error) {
// particular tower id. The index is reserved for that tower until // particular tower id. The index is reserved for that tower until
// CreateClientSession is invoked for that tower and index, at which point a new // CreateClientSession is invoked for that tower and index, at which point a new
// index for that tower can be reserved. Multiple calls to this method before // index for that tower can be reserved. Multiple calls to this method before
// CreateClientSession is invoked should return the same index. // CreateClientSession is invoked should return the same index unless forceNext
// is true.
func (c *ClientDB) NextSessionKeyIndex(towerID TowerID, func (c *ClientDB) NextSessionKeyIndex(towerID TowerID,
blobType blob.Type) (uint32, error) { blobType blob.Type, forceNext bool) (uint32, error) {
var index uint32 var index uint32
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error { err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
@@ -606,33 +655,48 @@ func (c *ClientDB) NextSessionKeyIndex(towerID TowerID,
return ErrUninitializedDB return ErrUninitializedDB
} }
// Check the session key index to see if a key has already been
// reserved for this tower. If so, we'll deserialize and return
// the index directly.
var err error var err error
index, err = getSessionKeyIndex(keyIndex, towerID, blobType) if !forceNext {
if err == nil { // Check the session key index to see if a key has
return nil // already been reserved for this tower. If so, we'll
// deserialize and return the index directly.
index, err = getSessionKeyIndex(
keyIndex, towerID, blobType,
)
if err == nil {
return nil
}
} }
// Otherwise, generate a new session key index since the node // By default, we use the next available bucket sequence as the
// doesn't already have reserved index. The error is ignored // key index. But if forceNext is true, then it is assumed that
// since NextSequence can't fail inside Update. // some data loss occurred and so the sequence is incremented a
index64, _ := keyIndex.NextSequence() // by a jump of 1000 so that we can arrive at a brand new key
// index quicker.
currentSequence := keyIndex.Sequence()
nextIndex := currentSequence + 1
if forceNext {
nextIndex = currentSequence + 1000
}
if err = keyIndex.SetSequence(nextIndex); err != nil {
return fmt.Errorf("could not set next bucket "+
"sequence: %w", err)
}
// As a sanity check, assert that the index is still in the // As a sanity check, assert that the index is still in the
// valid range of unhardened pubkeys. In the future, we should // valid range of unhardened pubkeys. In the future, we should
// move to only using hardened keys, and this will prevent any // move to only using hardened keys, and this will prevent any
// overlap from occurring until then. This also prevents us from // overlap from occurring until then. This also prevents us from
// overflowing uint32s. // overflowing uint32s.
if index64 > math.MaxInt32 { if nextIndex > math.MaxInt32 {
return fmt.Errorf("exhausted session key indexes") return fmt.Errorf("exhausted session key indexes")
} }
// Create the key that will used to be store the reserved index. // Create the key that will used to be store the reserved index.
keyBytes := createSessionKeyIndexKey(towerID, blobType) keyBytes := createSessionKeyIndexKey(towerID, blobType)
index = uint32(index64) index = uint32(nextIndex)
var indexBuf [4]byte var indexBuf [4]byte
byteOrder.PutUint32(indexBuf[:], index) byteOrder.PutUint32(indexBuf[:], index)
@@ -719,24 +783,58 @@ func (c *ClientDB) CreateClientSession(session *ClientSession) error {
} }
} }
// Add the new entry to the towerID-to-SessionID index. // Get the session-ID index bucket.
indexBkt := towerToSessionIndex.NestedReadWriteBucket( dbIDIndex := tx.ReadWriteBucket(cSessionIDIndexBkt)
towerID.Bytes(), if dbIDIndex == nil {
) return ErrUninitializedDB
if indexBkt == nil {
return ErrTowerNotFound
} }
err = indexBkt.Put(session.ID[:], []byte{1}) // Get a new, unique, ID for this session from the session-ID
// index bucket.
nextSeq, err := dbIDIndex.NextSequence()
if err != nil { if err != nil {
return err return err
} }
// Add the new entry to the dbID-to-SessionID index.
newIndex, err := writeBigSize(nextSeq)
if err != nil {
return err
}
err = dbIDIndex.Put(newIndex, session.ID[:])
if err != nil {
return err
}
// Also add the db-assigned-id to the session bucket under the
// cSessionDBID key.
sessionBkt, err := sessions.CreateBucket(session.ID[:]) sessionBkt, err := sessions.CreateBucket(session.ID[:])
if err != nil { if err != nil {
return err return err
} }
err = sessionBkt.Put(cSessionDBID, newIndex)
if err != nil {
return err
}
// TODO(elle): migrate the towerID-to-SessionID to use the
// new db-assigned sessionID's rather.
// Add the new entry to the towerID-to-SessionID index.
towerSessions := towerToSessionIndex.NestedReadWriteBucket(
towerID.Bytes(),
)
if towerSessions == nil {
return ErrTowerNotFound
}
err = towerSessions.Put(session.ID[:], []byte{1})
if err != nil {
return err
}
// Finally, write the client session's body in the sessions // Finally, write the client session's body in the sessions
// bucket. // bucket.
return putClientSessionBody(sessionBkt, session) return putClientSessionBody(sessionBkt, session)
@@ -956,6 +1054,37 @@ func getSessionKeyIndex(keyIndexes kvdb.RwBucket, towerID TowerID,
return byteOrder.Uint32(keyIndexBytes), nil return byteOrder.Uint32(keyIndexBytes), nil
} }
// GetClientSession loads the ClientSession with the given ID from the DB.
func (c *ClientDB) GetClientSession(id SessionID,
opts ...ClientSessionListOption) (*ClientSession, error) {
var sess *ClientSession
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, id[:], opts...,
)
if err != nil {
return err
}
sess = session
return nil
}, func() {})
return sess, err
}
// ListClientSessions returns the set of all client sessions known to the db. An // ListClientSessions returns the set of all client sessions known to the db. An
// optional tower ID can be used to filter out any client sessions in the // optional tower ID can be used to filter out any client sessions in the
// response that do not correspond to this tower. // response that do not correspond to this tower.
@@ -969,20 +1098,14 @@ func (c *ClientDB) ListClientSessions(id *TowerID,
return ErrUninitializedDB return ErrUninitializedDB
} }
towers := tx.ReadBucket(cTowerBkt)
if towers == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt) chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil { if chanIDIndexBkt == nil {
return ErrUninitializedDB return ErrUninitializedDB
} }
var err error
// If no tower ID is specified, then fetch all the sessions // If no tower ID is specified, then fetch all the sessions
// known to the db. // known to the db.
var err error
if id == nil { if id == nil {
clientSessions, err = c.listClientAllSessions( clientSessions, err = c.listClientAllSessions(
sessions, chanIDIndexBkt, opts..., sessions, chanIDIndexBkt, opts...,
@@ -1024,7 +1147,9 @@ func (c *ClientDB) listClientAllSessions(sessions, chanIDIndexBkt kvdb.RBucket,
session, err := c.getClientSession( session, err := c.getClientSession(
sessions, chanIDIndexBkt, k, opts..., sessions, chanIDIndexBkt, k, opts...,
) )
if err != nil { if errors.Is(err, ErrSessionFailedFilterFn) {
return nil
} else if err != nil {
return err return err
} }
@@ -1059,7 +1184,9 @@ func (c *ClientDB) listTowerSessions(id TowerID, sessionsBkt, chanIDIndexBkt,
session, err := c.getClientSession( session, err := c.getClientSession(
sessionsBkt, chanIDIndexBkt, k, opts..., sessionsBkt, chanIDIndexBkt, k, opts...,
) )
if err != nil { if errors.Is(err, ErrSessionFailedFilterFn) {
return nil
} else if err != nil {
return err return err
} }
@@ -1171,7 +1298,8 @@ func (c *ClientDB) NumAckedUpdates(id *SessionID) (uint64, error) {
} }
// FetchChanSummaries loads a mapping from all registered channels to their // FetchChanSummaries loads a mapping from all registered channels to their
// channel summaries. // channel summaries. Only the channels that have not yet been marked as closed
// will be loaded.
func (c *ClientDB) FetchChanSummaries() (ChannelSummaries, error) { func (c *ClientDB) FetchChanSummaries() (ChannelSummaries, error) {
var summaries map[lnwire.ChannelID]ClientChanSummary var summaries map[lnwire.ChannelID]ClientChanSummary
@@ -1187,6 +1315,13 @@ func (c *ClientDB) FetchChanSummaries() (ChannelSummaries, error) {
return ErrCorruptChanDetails return ErrCorruptChanDetails
} }
// If this channel has already been marked as closed,
// then its summary does not need to be loaded.
closedHeight := chanDetails.Get(cChanClosedHeight)
if len(closedHeight) > 0 {
return nil
}
var chanID lnwire.ChannelID var chanID lnwire.ChannelID
copy(chanID[:], k) copy(chanID[:], k)
@@ -1282,6 +1417,420 @@ func (c *ClientDB) MarkBackupIneligible(chanID lnwire.ChannelID,
return nil return nil
} }
// ListClosableSessions fetches and returns the IDs for all sessions marked as
// closable.
func (c *ClientDB) ListClosableSessions() (map[SessionID]uint32, error) {
sessions := make(map[SessionID]uint32)
err := kvdb.View(c.db, func(tx kvdb.RTx) error {
csBkt := tx.ReadBucket(cClosableSessionsBkt)
if csBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
return csBkt.ForEach(func(dbIDBytes, heightBytes []byte) error {
dbID, err := readBigSize(dbIDBytes)
if err != nil {
return err
}
sessID, err := getRealSessionID(sessIDIndexBkt, dbID)
if err != nil {
return err
}
sessions[*sessID] = byteOrder.Uint32(heightBytes)
return nil
})
}, func() {
sessions = make(map[SessionID]uint32)
})
if err != nil {
return nil, err
}
return sessions, nil
}
// DeleteSession can be called when a session should be deleted from the DB.
// All references to the session will also be deleted from the DB. Note that a
// session will only be deleted if was previously marked as closable.
func (c *ClientDB) DeleteSession(id SessionID) error {
return kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
closableBkt := tx.ReadWriteBucket(cClosableSessionsBkt)
if closableBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadWriteBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadWriteBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
towerToSessBkt := tx.ReadWriteBucket(cTowerToSessionIndexBkt)
if towerToSessBkt == nil {
return ErrUninitializedDB
}
// Get the sub-bucket for this session ID. If it does not exist
// then the session has already been deleted and so our work is
// done.
sessionBkt := sessionsBkt.NestedReadBucket(id[:])
if sessionBkt == nil {
return nil
}
_, dbIDBytes, err := getDBSessionID(sessionsBkt, id)
if err != nil {
return err
}
// First we check if the session has actually been marked as
// closable.
if closableBkt.Get(dbIDBytes) == nil {
return ErrSessionNotClosable
}
sess, err := getClientSessionBody(sessionsBkt, id[:])
if err != nil {
return err
}
// Delete from the tower-to-sessionID index.
towerIndexBkt := towerToSessBkt.NestedReadWriteBucket(
sess.TowerID.Bytes(),
)
if towerIndexBkt == nil {
return fmt.Errorf("no entry in the tower-to-session "+
"index found for tower ID %v", sess.TowerID)
}
err = towerIndexBkt.Delete(id[:])
if err != nil {
return err
}
// Delete entry from session ID index.
err = sessIDIndexBkt.Delete(dbIDBytes)
if err != nil {
return err
}
// Delete the entry from the closable sessions index.
err = closableBkt.Delete(dbIDBytes)
if err != nil {
return err
}
// Get the acked updates range index for the session. This is
// used to get the list of channels that the session has updates
// for.
ackRanges := sessionBkt.NestedReadBucket(cSessionAckRangeIndex)
if ackRanges == nil {
// A session would only be considered closable if it
// was exhausted. Meaning that it should not be the
// case that it has no acked-updates.
return fmt.Errorf("cannot delete session %s since it "+
"is not yet exhausted", id)
}
// For each of the channels, delete the session ID entry.
err = ackRanges.ForEach(func(chanDBID, _ []byte) error {
chanDBIDInt, err := readBigSize(chanDBID)
if err != nil {
return err
}
chanID, err := getRealChannelID(
chanIDIndexBkt, chanDBIDInt,
)
if err != nil {
return err
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(
chanID[:],
)
if chanDetails == nil {
return ErrChannelNotRegistered
}
chanSessions := chanDetails.NestedReadWriteBucket(
cChanSessions,
)
if chanSessions == nil {
return fmt.Errorf("no session list found for "+
"channel %s", chanID)
}
// Check that this session was actually listed in the
// session list for this channel.
if len(chanSessions.Get(dbIDBytes)) == 0 {
return fmt.Errorf("session %s not found in "+
"the session list for channel %s", id,
chanID)
}
// If it was, then delete it.
err = chanSessions.Delete(dbIDBytes)
if err != nil {
return err
}
// If this was the last session for this channel, we can
// now delete the channel details for this channel
// completely.
err = chanSessions.ForEach(func(_, _ []byte) error {
return errChannelHasMoreSessions
})
if errors.Is(err, errChannelHasMoreSessions) {
return nil
} else if err != nil {
return err
}
// Delete the channel's entry from the channel-id-index.
dbID := chanDetails.Get(cChanDBID)
err = chanIDIndexBkt.Delete(dbID)
if err != nil {
return err
}
// Delete the channel details.
return chanDetailsBkt.DeleteNestedBucket(chanID[:])
})
if err != nil {
return err
}
// Delete the actual session.
return sessionsBkt.DeleteNestedBucket(id[:])
}, func() {})
}
// MarkChannelClosed will mark a registered channel as closed by setting its
// closed-height as the given block height. It returns a list of session IDs for
// sessions that are now considered closable due to the close of this channel.
// The details for this channel will be deleted from the DB if there are no more
// sessions in the DB that contain updates for this channel.
func (c *ClientDB) MarkChannelClosed(chanID lnwire.ChannelID,
blockHeight uint32) ([]SessionID, error) {
var closableSessions []SessionID
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
closableSessBkt := tx.ReadWriteBucket(cClosableSessionsBkt)
if closableSessBkt == nil {
return ErrUninitializedDB
}
chanIDIndexBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDIndexBkt == nil {
return ErrUninitializedDB
}
sessIDIndexBkt := tx.ReadBucket(cSessionIDIndexBkt)
if sessIDIndexBkt == nil {
return ErrUninitializedDB
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(chanID[:])
if chanDetails == nil {
return ErrChannelNotRegistered
}
// If there are no sessions for this channel, the channel
// details can be deleted.
chanSessIDsBkt := chanDetails.NestedReadBucket(cChanSessions)
if chanSessIDsBkt == nil {
return chanDetailsBkt.DeleteNestedBucket(chanID[:])
}
// Otherwise, mark the channel as closed.
var height [4]byte
byteOrder.PutUint32(height[:], blockHeight)
err := chanDetails.Put(cChanClosedHeight, height[:])
if err != nil {
return err
}
// Now iterate through all the sessions of the channel to check
// if any of them are closeable.
return chanSessIDsBkt.ForEach(func(sessDBID, _ []byte) error {
sessDBIDInt, err := readBigSize(sessDBID)
if err != nil {
return err
}
// Use the session-ID index to get the real session ID.
sID, err := getRealSessionID(
sessIDIndexBkt, sessDBIDInt,
)
if err != nil {
return err
}
isClosable, err := isSessionClosable(
sessionsBkt, chanDetailsBkt, chanIDIndexBkt,
sID,
)
if err != nil {
return err
}
if !isClosable {
return nil
}
// Add session to "closableSessions" list and add the
// block height that this last channel was closed in.
// This will be used in future to determine when we
// should delete the session.
var height [4]byte
byteOrder.PutUint32(height[:], blockHeight)
err = closableSessBkt.Put(sessDBID, height[:])
if err != nil {
return err
}
closableSessions = append(closableSessions, *sID)
return nil
})
}, func() {
closableSessions = nil
})
if err != nil {
return nil, err
}
return closableSessions, nil
}
// isSessionClosable returns true if a session is considered closable. A session
// is considered closable only if all the following points are true:
// 1) It has no un-acked updates.
// 2) It is exhausted (ie it can't accept any more updates)
// 3) All the channels that it has acked updates for are closed.
func isSessionClosable(sessionsBkt, chanDetailsBkt, chanIDIndexBkt kvdb.RBucket,
id *SessionID) (bool, error) {
sessBkt := sessionsBkt.NestedReadBucket(id[:])
if sessBkt == nil {
return false, ErrSessionNotFound
}
commitsBkt := sessBkt.NestedReadBucket(cSessionCommits)
if commitsBkt == nil {
// If the session has no cSessionCommits bucket then we can be
// sure that no updates have ever been committed to the session
// and so it is not yet exhausted.
return false, nil
}
// If the session has any un-acked updates, then it is not yet closable.
err := commitsBkt.ForEach(func(_, _ []byte) error {
return errSessionHasUnackedUpdates
})
if errors.Is(err, errSessionHasUnackedUpdates) {
return false, nil
} else if err != nil {
return false, err
}
session, err := getClientSessionBody(sessionsBkt, id[:])
if err != nil {
return false, err
}
// We have already checked that the session has no more committed
// updates. So now we can check if the session is exhausted.
if session.SeqNum < session.Policy.MaxUpdates {
// If the session is not yet exhausted, it is not yet closable.
return false, nil
}
// If the session has no acked-updates, then something is wrong since
// the above check ensures that this session has been exhausted meaning
// that it should have MaxUpdates acked updates.
ackedRangeBkt := sessBkt.NestedReadBucket(cSessionAckRangeIndex)
if ackedRangeBkt == nil {
return false, fmt.Errorf("no acked-updates found for "+
"exhausted session %s", id)
}
// Iterate over each of the channels that the session has acked-updates
// for. If any of those channels are not closed, then the session is
// not yet closable.
err = ackedRangeBkt.ForEach(func(dbChanID, _ []byte) error {
dbChanIDInt, err := readBigSize(dbChanID)
if err != nil {
return err
}
chanID, err := getRealChannelID(chanIDIndexBkt, dbChanIDInt)
if err != nil {
return err
}
// Get the channel details bucket for the channel.
chanDetails := chanDetailsBkt.NestedReadBucket(chanID[:])
if chanDetails == nil {
return fmt.Errorf("no channel details found for "+
"channel %s referenced by session %s", chanID,
id)
}
// If a closed height has been set, then the channel is closed.
closedHeight := chanDetails.Get(cChanClosedHeight)
if len(closedHeight) > 0 {
return nil
}
// Otherwise, the channel is not yet closed meaning that the
// session is not yet closable. We break the ForEach by
// returning an error to indicate this.
return errSessionHasOpenChannels
})
if errors.Is(err, errSessionHasOpenChannels) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}
// CommitUpdate persists the CommittedUpdate provided in the slot for (session, // CommitUpdate persists the CommittedUpdate provided in the slot for (session,
// seqNum). This allows the client to retransmit this update on startup. // seqNum). This allows the client to retransmit this update on startup.
func (c *ClientDB) CommitUpdate(id *SessionID, func (c *ClientDB) CommitUpdate(id *SessionID,
@@ -1400,7 +1949,7 @@ func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
return ErrUninitializedDB return ErrUninitializedDB
} }
chanDetailsBkt := tx.ReadBucket(cChanDetailsBkt) chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil { if chanDetailsBkt == nil {
return ErrUninitializedDB return ErrUninitializedDB
} }
@@ -1484,6 +2033,23 @@ func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
return err return err
} }
dbSessionID, _, err := getDBSessionID(sessions, *id)
if err != nil {
return err
}
chanDetails := chanDetailsBkt.NestedReadWriteBucket(
committedUpdate.BackupID.ChanID[:],
)
if chanDetails == nil {
return ErrChannelNotRegistered
}
err = putChannelToSessionMapping(chanDetails, dbSessionID)
if err != nil {
return err
}
// Get the range index for the given session-channel pair. // Get the range index for the given session-channel pair.
index, err := c.getRangeIndex(tx, *id, chanID) index, err := c.getRangeIndex(tx, *id, chanID)
if err != nil { if err != nil {
@@ -1494,6 +2060,26 @@ func (c *ClientDB) AckUpdate(id *SessionID, seqNum uint16,
}, func() {}) }, func() {})
} }
// putChannelToSessionMapping adds the given session ID to a channel's
// cChanSessions bucket.
func putChannelToSessionMapping(chanDetails kvdb.RwBucket,
dbSessID uint64) error {
chanSessIDsBkt, err := chanDetails.CreateBucketIfNotExists(
cChanSessions,
)
if err != nil {
return err
}
b, err := writeBigSize(dbSessID)
if err != nil {
return err
}
return chanSessIDsBkt.Put(b, []byte{1})
}
// getClientSessionBody loads the body of a ClientSession from the sessions // getClientSessionBody loads the body of a ClientSession from the sessions
// bucket corresponding to the serialized session id. This does not deserialize // bucket corresponding to the serialized session id. This does not deserialize
// the CommittedUpdates, AckUpdates or the Tower associated with the session. // the CommittedUpdates, AckUpdates or the Tower associated with the session.
@@ -1523,6 +2109,11 @@ func getClientSessionBody(sessions kvdb.RBucket,
return &session, nil return &session, nil
} }
// ClientSessionFilterFn describes the signature of a callback function that can
// be used to filter the sessions that are returned in any of the DB methods
// that read sessions from the DB.
type ClientSessionFilterFn func(*ClientSession) bool
// PerMaxHeightCB describes the signature of a callback function that can be // PerMaxHeightCB describes the signature of a callback function that can be
// called for each channel that a session has updates for to communicate the // called for each channel that a session has updates for to communicate the
// maximum commitment height that the session has backed up for the channel. // maximum commitment height that the session has backed up for the channel.
@@ -1533,6 +2124,10 @@ type PerMaxHeightCB func(*ClientSession, lnwire.ChannelID, uint64)
// number of updates that the session has for the channel. // number of updates that the session has for the channel.
type PerNumAckedUpdatesCB func(*ClientSession, lnwire.ChannelID, uint16) type PerNumAckedUpdatesCB func(*ClientSession, lnwire.ChannelID, uint16)
// PerAckedUpdateCB describes the signature of a callback function that can be
// called for each of a session's acked updates.
type PerAckedUpdateCB func(*ClientSession, uint16, BackupID)
// PerCommittedUpdateCB describes the signature of a callback function that can // PerCommittedUpdateCB describes the signature of a callback function that can
// be called for each of a session's committed updates (updates that the client // be called for each of a session's committed updates (updates that the client
// has not yet received an ACK for). // has not yet received an ACK for).
@@ -1559,6 +2154,19 @@ type ClientSessionListCfg struct {
// PerCommittedUpdate will, if set, be called for each of the session's // PerCommittedUpdate will, if set, be called for each of the session's
// committed (un-acked) updates. // committed (un-acked) updates.
PerCommittedUpdate PerCommittedUpdateCB PerCommittedUpdate PerCommittedUpdateCB
// PreEvaluateFilterFn will be run after loading a session from the DB
// and _before_ any of the other call-back functions in
// ClientSessionListCfg. Therefore, if a session fails this filter
// function, then it will not be passed to any of the other call backs
// and won't be included in the return list.
PreEvaluateFilterFn ClientSessionFilterFn
// PostEvaluateFilterFn will be run _after_ all the other call-back
// functions in ClientSessionListCfg. If a session fails this filter
// function then all it means is that it won't be included in the list
// of sessions to return.
PostEvaluateFilterFn ClientSessionFilterFn
} }
// NewClientSessionCfg constructs a new ClientSessionListCfg. // NewClientSessionCfg constructs a new ClientSessionListCfg.
@@ -1593,6 +2201,29 @@ func WithPerCommittedUpdate(cb PerCommittedUpdateCB) ClientSessionListOption {
} }
} }
// WithPreEvalFilterFn constructs a functional option that will set a call-back
// function that will be called immediately after loading a session. If the
// session fails this filter function, then it will not be passed to any of the
// other evaluation call-back functions.
func WithPreEvalFilterFn(fn ClientSessionFilterFn) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PreEvaluateFilterFn = fn
}
}
// WithPostEvalFilterFn constructs a functional option that will set a call-back
// function that will be used to determine if a session should be included in
// the returned list. This differs from WithPreEvalFilterFn since that call-back
// is used to determine if the session should be evaluated at all (and thus
// run against the other ClientSessionListCfg call-backs) whereas the session
// will only reach the PostEvalFilterFn call-back once it has already been
// evaluated by all the other call-backs.
func WithPostEvalFilterFn(fn ClientSessionFilterFn) ClientSessionListOption {
return func(cfg *ClientSessionListCfg) {
cfg.PostEvaluateFilterFn = fn
}
}
// getClientSession loads the full ClientSession associated with the serialized // getClientSession loads the full ClientSession associated with the serialized
// session id. This method populates the CommittedUpdates, AckUpdates and Tower // session id. This method populates the CommittedUpdates, AckUpdates and Tower
// in addition to the ClientSession's body. // in addition to the ClientSession's body.
@@ -1610,6 +2241,10 @@ func (c *ClientDB) getClientSession(sessionsBkt, chanIDIndexBkt kvdb.RBucket,
return nil, err return nil, err
} }
if cfg.PreEvaluateFilterFn != nil && !cfg.PreEvaluateFilterFn(session) {
return nil, ErrSessionFailedFilterFn
}
// Can't fail because client session body has already been read. // Can't fail because client session body has already been read.
sessionBkt := sessionsBkt.NestedReadBucket(idBytes) sessionBkt := sessionsBkt.NestedReadBucket(idBytes)
@@ -1632,6 +2267,12 @@ func (c *ClientDB) getClientSession(sessionsBkt, chanIDIndexBkt kvdb.RBucket,
return nil, err return nil, err
} }
if cfg.PostEvaluateFilterFn != nil &&
!cfg.PostEvaluateFilterFn(session) {
return nil, ErrSessionFailedFilterFn
}
return session, nil return session, nil
} }
@@ -1859,6 +2500,68 @@ func getDBChanID(chanDetailsBkt kvdb.RBucket, chanID lnwire.ChannelID) (uint64,
return id, idBytes, nil return id, idBytes, nil
} }
// getDBSessionID returns the db-assigned session ID for the given real session
// ID. It returns both the uint64 and byte representation.
func getDBSessionID(sessionsBkt kvdb.RBucket, sessionID SessionID) (uint64,
[]byte, error) {
sessionBkt := sessionsBkt.NestedReadBucket(sessionID[:])
if sessionBkt == nil {
return 0, nil, ErrClientSessionNotFound
}
idBytes := sessionBkt.Get(cSessionDBID)
if len(idBytes) == 0 {
return 0, nil, fmt.Errorf("no db-assigned ID found for "+
"session ID %s", sessionID)
}
id, err := readBigSize(idBytes)
if err != nil {
return 0, nil, err
}
return id, idBytes, nil
}
func getRealSessionID(sessIDIndexBkt kvdb.RBucket, dbID uint64) (*SessionID,
error) {
dbIDBytes, err := writeBigSize(dbID)
if err != nil {
return nil, err
}
sessIDBytes := sessIDIndexBkt.Get(dbIDBytes)
if len(sessIDBytes) != SessionIDSize {
return nil, fmt.Errorf("session ID not found")
}
var sessID SessionID
copy(sessID[:], sessIDBytes)
return &sessID, nil
}
func getRealChannelID(chanIDIndexBkt kvdb.RBucket,
dbID uint64) (*lnwire.ChannelID, error) {
dbIDBytes, err := writeBigSize(dbID)
if err != nil {
return nil, err
}
chanIDBytes := chanIDIndexBkt.Get(dbIDBytes)
if len(chanIDBytes) != 32 { //nolint:gomnd
return nil, fmt.Errorf("channel ID not found")
}
var chanIDS lnwire.ChannelID
copy(chanIDS[:], chanIDBytes)
return &chanIDS, nil
}
// writeBigSize will encode the given uint64 as a BigSize byte slice. // writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) { func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer var b bytes.Buffer
+221 -13
View File
@@ -3,6 +3,7 @@ package wtdb_test
import ( import (
crand "crypto/rand" crand "crypto/rand"
"io" "io"
"math/rand"
"net" "net"
"testing" "testing"
@@ -17,6 +18,8 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const blobType = blob.TypeAltruistCommit
// pseudoAddr is a fake network address to be used for testing purposes. // pseudoAddr is a fake network address to be used for testing purposes.
var pseudoAddr = &net.TCPAddr{IP: []byte{0x01, 0x00, 0x00, 0x00}, Port: 9911} var pseudoAddr = &net.TCPAddr{IP: []byte{0x01, 0x00, 0x00, 0x00}, Port: 9911}
@@ -59,12 +62,12 @@ func (h *clientDBHarness) listSessions(id *wtdb.TowerID,
return sessions return sessions
} }
func (h *clientDBHarness) nextKeyIndex(id wtdb.TowerID, func (h *clientDBHarness) nextKeyIndex(id wtdb.TowerID, blobType blob.Type,
blobType blob.Type) uint32 { forceNext bool) uint32 {
h.t.Helper() h.t.Helper()
index, err := h.db.NextSessionKeyIndex(id, blobType) index, err := h.db.NextSessionKeyIndex(id, blobType, forceNext)
require.NoError(h.t, err, "unable to create next session key index") require.NoError(h.t, err, "unable to create next session key index")
require.NotZero(h.t, index, "next key index should never be 0") require.NotZero(h.t, index, "next key index should never be 0")
@@ -192,6 +195,35 @@ func (h *clientDBHarness) ackUpdate(id *wtdb.SessionID, seqNum uint16,
require.ErrorIs(h.t, err, expErr) require.ErrorIs(h.t, err, expErr)
} }
func (h *clientDBHarness) markChannelClosed(id lnwire.ChannelID,
blockHeight uint32, expErr error) []wtdb.SessionID {
h.t.Helper()
closableSessions, err := h.db.MarkChannelClosed(id, blockHeight)
require.ErrorIs(h.t, err, expErr)
return closableSessions
}
func (h *clientDBHarness) listClosableSessions(
expErr error) map[wtdb.SessionID]uint32 {
h.t.Helper()
closableSessions, err := h.db.ListClosableSessions()
require.ErrorIs(h.t, err, expErr)
return closableSessions
}
func (h *clientDBHarness) deleteSession(id wtdb.SessionID, expErr error) {
h.t.Helper()
err := h.db.DeleteSession(id)
require.ErrorIs(h.t, err, expErr)
}
// newTower is a helper function that creates a new tower with a randomly // newTower is a helper function that creates a new tower with a randomly
// generated public key and inserts it into the client DB. // generated public key and inserts it into the client DB.
func (h *clientDBHarness) newTower() *wtdb.Tower { func (h *clientDBHarness) newTower() *wtdb.Tower {
@@ -277,7 +309,7 @@ func testCreateClientSession(h *clientDBHarness) {
h.insertSession(session, wtdb.ErrNoReservedKeyIndex) h.insertSession(session, wtdb.ErrNoReservedKeyIndex)
// Now, reserve a session key for this tower. // Now, reserve a session key for this tower.
keyIndex := h.nextKeyIndex(session.TowerID, blobType) keyIndex := h.nextKeyIndex(session.TowerID, blobType, false)
// The client session hasn't been updated with the reserved key index // The client session hasn't been updated with the reserved key index
// (since it's still zero). Inserting should fail due to the mismatch. // (since it's still zero). Inserting should fail due to the mismatch.
@@ -286,7 +318,7 @@ func testCreateClientSession(h *clientDBHarness) {
// Reserve another key for the same index. Since no session has been // Reserve another key for the same index. Since no session has been
// successfully created, it should return the same index to maintain // successfully created, it should return the same index to maintain
// idempotency across restarts. // idempotency across restarts.
keyIndex2 := h.nextKeyIndex(session.TowerID, blobType) keyIndex2 := h.nextKeyIndex(session.TowerID, blobType, false)
require.Equalf(h.t, keyIndex, keyIndex2, "next key index should "+ require.Equalf(h.t, keyIndex, keyIndex2, "next key index should "+
"be idempotent: want: %v, got %v", keyIndex, keyIndex2) "be idempotent: want: %v, got %v", keyIndex, keyIndex2)
@@ -303,11 +335,23 @@ func testCreateClientSession(h *clientDBHarness) {
// session already existing. // session already existing.
h.insertSession(session, wtdb.ErrClientSessionAlreadyExists) h.insertSession(session, wtdb.ErrClientSessionAlreadyExists)
// Finally, assert that reserving another key index succeeds with a // Assert that reserving another key index succeeds with a different key
// different key index, now that the first one has been finalized. // index, now that the first one has been finalized.
keyIndex3 := h.nextKeyIndex(session.TowerID, blobType) keyIndex3 := h.nextKeyIndex(session.TowerID, blobType, false)
require.NotEqualf(h.t, keyIndex, keyIndex3, "key index still "+ require.NotEqualf(h.t, keyIndex, keyIndex3, "key index still "+
"reserved after creating session") "reserved after creating session")
// Show that calling NextSessionKeyIndex again now will result in the
// same key being returned as long as forceNext remains false.
keyIndex4 := h.nextKeyIndex(session.TowerID, blobType, false)
require.Equal(h.t, keyIndex3, keyIndex4)
// Finally, assert that if the forceNext param of the
// NextSessionKeyIndex method is true, then the key index returned will
// be different.
keyIndex5 := h.nextKeyIndex(session.TowerID, blobType, true)
require.NotEqual(h.t, keyIndex5, keyIndex4)
require.Equal(h.t, keyIndex3+1000, keyIndex5)
} }
// testFilterClientSessions asserts that we can correctly filter client sessions // testFilterClientSessions asserts that we can correctly filter client sessions
@@ -320,7 +364,7 @@ func testFilterClientSessions(h *clientDBHarness) {
towerSessions := make(map[wtdb.TowerID][]wtdb.SessionID) towerSessions := make(map[wtdb.TowerID][]wtdb.SessionID)
for i := 0; i < numSessions; i++ { for i := 0; i < numSessions; i++ {
tower := h.newTower() tower := h.newTower()
keyIndex := h.nextKeyIndex(tower.ID, blobType) keyIndex := h.nextKeyIndex(tower.ID, blobType, false)
sessionID := wtdb.SessionID([33]byte{byte(i)}) sessionID := wtdb.SessionID([33]byte{byte(i)})
h.insertSession(&wtdb.ClientSession{ h.insertSession(&wtdb.ClientSession{
ClientSessionBody: wtdb.ClientSessionBody{ ClientSessionBody: wtdb.ClientSessionBody{
@@ -467,7 +511,9 @@ func testRemoveTower(h *clientDBHarness) {
MaxUpdates: 100, MaxUpdates: 100,
}, },
RewardPkScript: []byte{0x01, 0x02, 0x03}, RewardPkScript: []byte{0x01, 0x02, 0x03},
KeyIndex: h.nextKeyIndex(tower.ID, blobType), KeyIndex: h.nextKeyIndex(
tower.ID, blobType, false,
),
}, },
ID: wtdb.SessionID([33]byte{0x01}), ID: wtdb.SessionID([33]byte{0x01}),
} }
@@ -549,7 +595,7 @@ func testCommitUpdate(h *clientDBHarness) {
) )
// Reserve a session key index and insert the session. // Reserve a session key index and insert the session.
session.KeyIndex = h.nextKeyIndex(session.TowerID, blobType) session.KeyIndex = h.nextKeyIndex(session.TowerID, blobType, false)
h.insertSession(session, nil) h.insertSession(session, nil)
// Now, try to commit the update that failed initially which should // Now, try to commit the update that failed initially which should
@@ -604,6 +650,118 @@ func testCommitUpdate(h *clientDBHarness) {
}, nil) }, nil)
} }
// testMarkChannelClosed asserts the behaviour of MarkChannelClosed.
func testMarkChannelClosed(h *clientDBHarness) {
tower := h.newTower()
// Create channel 1.
chanID1 := randChannelID(h.t)
// Since we have not yet registered the channel, we expect an error
// when attempting to mark it as closed.
h.markChannelClosed(chanID1, 1, wtdb.ErrChannelNotRegistered)
// Now register the channel.
h.registerChan(chanID1, nil, nil)
// Since there are still no sessions that would have updates for the
// channel, marking it as closed now should succeed.
h.markChannelClosed(chanID1, 1, nil)
// Register channel 2.
chanID2 := randChannelID(h.t)
h.registerChan(chanID2, nil, nil)
// Create session1 with MaxUpdates set to 5.
session1 := h.randSession(h.t, tower.ID, 5)
h.insertSession(session1, nil)
// Add an update for channel 2 in session 1 and ack it too.
update := randCommittedUpdateForChannel(h.t, chanID2, 1)
lastApplied := h.commitUpdate(&session1.ID, update, nil)
require.Zero(h.t, lastApplied)
h.ackUpdate(&session1.ID, 1, 1, nil)
// Marking channel 2 now should not result in any closable sessions
// since session 1 is not yet exhausted.
sl := h.markChannelClosed(chanID2, 1, nil)
require.Empty(h.t, sl)
// Create channel 3 and 4.
chanID3 := randChannelID(h.t)
h.registerChan(chanID3, nil, nil)
chanID4 := randChannelID(h.t)
h.registerChan(chanID4, nil, nil)
// Add an update for channel 4 and ack it.
update = randCommittedUpdateForChannel(h.t, chanID4, 2)
lastApplied = h.commitUpdate(&session1.ID, update, nil)
require.EqualValues(h.t, 1, lastApplied)
h.ackUpdate(&session1.ID, 2, 2, nil)
// Add an update for channel 3 in session 1. But dont ack it yet.
update = randCommittedUpdateForChannel(h.t, chanID2, 3)
lastApplied = h.commitUpdate(&session1.ID, update, nil)
require.EqualValues(h.t, 2, lastApplied)
// Mark channel 4 as closed & assert that session 1 is not seen as
// closable since it still has committed updates.
sl = h.markChannelClosed(chanID4, 1, nil)
require.Empty(h.t, sl)
// Now ack the update we added above.
h.ackUpdate(&session1.ID, 3, 3, nil)
// Mark channel 3 as closed & assert that session 1 is still not seen as
// closable since it is not yet exhausted.
sl = h.markChannelClosed(chanID3, 1, nil)
require.Empty(h.t, sl)
// Create channel 5 and 6.
chanID5 := randChannelID(h.t)
h.registerChan(chanID5, nil, nil)
chanID6 := randChannelID(h.t)
h.registerChan(chanID6, nil, nil)
// Add an update for channel 5 and ack it.
update = randCommittedUpdateForChannel(h.t, chanID5, 4)
lastApplied = h.commitUpdate(&session1.ID, update, nil)
require.EqualValues(h.t, 3, lastApplied)
h.ackUpdate(&session1.ID, 4, 4, nil)
// Add an update for channel 6 and ack it.
update = randCommittedUpdateForChannel(h.t, chanID6, 5)
lastApplied = h.commitUpdate(&session1.ID, update, nil)
require.EqualValues(h.t, 4, lastApplied)
h.ackUpdate(&session1.ID, 5, 5, nil)
// The session is no exhausted.
// If we now close channel 5, session 1 should still not be closable
// since it has an update for channel 6 which is still open.
sl = h.markChannelClosed(chanID5, 1, nil)
require.Empty(h.t, sl)
require.Empty(h.t, h.listClosableSessions(nil))
// Also check that attempting to delete the session will fail since it
// is not yet considered closable.
h.deleteSession(session1.ID, wtdb.ErrSessionNotClosable)
// Finally, if we close channel 6, session 1 _should_ be in the closable
// list.
sl = h.markChannelClosed(chanID6, 100, nil)
require.ElementsMatch(h.t, sl, []wtdb.SessionID{session1.ID})
slMap := h.listClosableSessions(nil)
require.InDeltaMapValues(h.t, slMap, map[wtdb.SessionID]uint32{
session1.ID: 100,
}, 0)
// Assert that we now can delete the session.
h.deleteSession(session1.ID, nil)
require.Empty(h.t, h.listClosableSessions(nil))
}
// testAckUpdate asserts the behavior of AckUpdate. // testAckUpdate asserts the behavior of AckUpdate.
func testAckUpdate(h *clientDBHarness) { func testAckUpdate(h *clientDBHarness) {
const blobType = blob.TypeAltruistCommit const blobType = blob.TypeAltruistCommit
@@ -630,7 +788,7 @@ func testAckUpdate(h *clientDBHarness) {
h.ackUpdate(&session.ID, 1, 0, wtdb.ErrClientSessionNotFound) h.ackUpdate(&session.ID, 1, 0, wtdb.ErrClientSessionNotFound)
// Reserve a session key and insert the client session. // Reserve a session key and insert the client session.
session.KeyIndex = h.nextKeyIndex(session.TowerID, blobType) session.KeyIndex = h.nextKeyIndex(session.TowerID, blobType, false)
h.insertSession(session, nil) h.insertSession(session, nil)
// Now, try to ack update 1. This should fail since update 1 was never // Now, try to ack update 1. This should fail since update 1 was never
@@ -820,6 +978,10 @@ func TestClientDB(t *testing.T) {
name: "ack update", name: "ack update",
run: testAckUpdate, run: testAckUpdate,
}, },
{
name: "mark channel closed",
run: testMarkChannelClosed,
},
} }
for _, database := range dbs { for _, database := range dbs {
@@ -840,12 +1002,32 @@ func TestClientDB(t *testing.T) {
// randCommittedUpdate generates a random committed update. // randCommittedUpdate generates a random committed update.
func randCommittedUpdate(t *testing.T, seqNum uint16) *wtdb.CommittedUpdate { func randCommittedUpdate(t *testing.T, seqNum uint16) *wtdb.CommittedUpdate {
t.Helper()
chanID := randChannelID(t)
return randCommittedUpdateForChannel(t, chanID, seqNum)
}
func randChannelID(t *testing.T) lnwire.ChannelID {
t.Helper()
var chanID lnwire.ChannelID var chanID lnwire.ChannelID
_, err := io.ReadFull(crand.Reader, chanID[:]) _, err := io.ReadFull(crand.Reader, chanID[:])
require.NoError(t, err) require.NoError(t, err)
return chanID
}
// randCommittedUpdateForChannel generates a random committed update for the
// given channel ID.
func randCommittedUpdateForChannel(t *testing.T, chanID lnwire.ChannelID,
seqNum uint16) *wtdb.CommittedUpdate {
t.Helper()
var hint blob.BreachHint var hint blob.BreachHint
_, err = io.ReadFull(crand.Reader, hint[:]) _, err := io.ReadFull(crand.Reader, hint[:])
require.NoError(t, err) require.NoError(t, err)
encBlob := make([]byte, blob.Size(blob.FlagCommitOutputs.Type())) encBlob := make([]byte, blob.Size(blob.FlagCommitOutputs.Type()))
@@ -864,3 +1046,29 @@ func randCommittedUpdate(t *testing.T, seqNum uint16) *wtdb.CommittedUpdate {
}, },
} }
} }
func (h *clientDBHarness) randSession(t *testing.T,
towerID wtdb.TowerID, maxUpdates uint16) *wtdb.ClientSession {
t.Helper()
var id wtdb.SessionID
rand.Read(id[:])
return &wtdb.ClientSession{
ClientSessionBody: wtdb.ClientSessionBody{
TowerID: towerID,
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: blobType,
},
MaxUpdates: maxUpdates,
},
RewardPkScript: []byte{0x01, 0x02, 0x03},
KeyIndex: h.nextKeyIndex(
towerID, blobType, false,
),
},
ID: id,
}
}
+4
View File
@@ -8,6 +8,8 @@ import (
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration3" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration3"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration4" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration4"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration5" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration5"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration6"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration7"
) )
// log is a logger that is initialized with no output filters. This // log is a logger that is initialized with no output filters. This
@@ -36,6 +38,8 @@ func UseLogger(logger btclog.Logger) {
migration3.UseLogger(logger) migration3.UseLogger(logger)
migration4.UseLogger(logger) migration4.UseLogger(logger)
migration5.UseLogger(logger) migration5.UseLogger(logger)
migration6.UseLogger(logger)
migration7.UseLogger(logger)
} }
// logClosure is used to provide a closure over expensive logging operations so // logClosure is used to provide a closure over expensive logging operations so
+114
View File
@@ -0,0 +1,114 @@
package migration6
import (
"bytes"
"encoding/binary"
"errors"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAcks => seqnum -> encoded BackupID
cSessionBkt = []byte("client-session-bucket")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-id of a session.
cSessionDBID = []byte("client-session-db-id")
// cSessionIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> session-id
cSessionIDIndexBkt = []byte("client-session-id-index")
// cSessionBody is a sub-bucket of cSessionBkt storing only the body of
// the ClientSession.
cSessionBody = []byte("client-session-body")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
byteOrder = binary.BigEndian
)
// MigrateSessionIDIndex adds a new session ID index to the tower client db.
// This index is a mapping from db-assigned ID (a uint64 encoded using BigSize)
// to real session ID (33 bytes). This mapping will allow us to persist session
// pointers with fewer bytes in the future.
func MigrateSessionIDIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client db to add a new session ID " +
"index which stores a mapping from db-assigned ID to real " +
"session ID")
// Create a new top-level bucket for the index.
indexBkt, err := tx.CreateTopLevelBucket(cSessionIDIndexBkt)
if err != nil {
return err
}
// Get the existing top-level sessions bucket.
sessionsBkt := tx.ReadWriteBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
// Iterate over the sessions bucket where each key is a session-ID.
return sessionsBkt.ForEach(func(sessionID, _ []byte) error {
// Ask the DB for a new, unique, id for the index bucket.
nextSeq, err := indexBkt.NextSequence()
if err != nil {
return err
}
newIndex, err := writeBigSize(nextSeq)
if err != nil {
return err
}
// Add the new db-assigned-ID to real-session-ID pair to the
// new index bucket.
err = indexBkt.Put(newIndex, sessionID)
if err != nil {
return err
}
// Get the sub-bucket for this specific session ID.
sessionBkt := sessionsBkt.NestedReadWriteBucket(sessionID)
if sessionBkt == nil {
return ErrCorruptClientSession
}
// Here we ensure that the session bucket includes a session
// body. The only reason we do this is so that we can simulate
// a migration fail in a test to ensure that a migration fail
// results in an untouched db.
sessionBodyBytes := sessionBkt.Get(cSessionBody)
if sessionBodyBytes == nil {
return ErrCorruptClientSession
}
// Add the db-assigned ID of the session to the session under
// the cSessionDBID key.
return sessionBkt.Put(cSessionDBID, newIndex)
})
}
// writeBigSize will encode the given uint64 as a BigSize byte slice.
func writeBigSize(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
@@ -0,0 +1,147 @@
package migration6
import (
"bytes"
"testing"
"github.com/lightningnetwork/lnd/channeldb/migtest"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// pre is the expected data in the sessions bucket before the migration.
pre = map[string]interface{}{
sessionIDToString(100): map[string]interface{}{
string(cSessionBody): string([]byte{1, 2, 3}),
},
sessionIDToString(222): map[string]interface{}{
string(cSessionBody): string([]byte{4, 5, 6}),
},
}
// preFailCorruptDB should fail the migration due to no session body
// being found for a given session ID.
preFailCorruptDB = map[string]interface{}{
sessionIDToString(100): "",
}
// post is the expected session index after migration.
postIndex = map[string]interface{}{
indexToString(1): sessionIDToString(100),
indexToString(2): sessionIDToString(222),
}
// postSessions is the expected data in the sessions bucket after the
// migration.
postSessions = map[string]interface{}{
sessionIDToString(100): map[string]interface{}{
string(cSessionBody): string([]byte{1, 2, 3}),
string(cSessionDBID): indexToString(1),
},
sessionIDToString(222): map[string]interface{}{
string(cSessionBody): string([]byte{4, 5, 6}),
string(cSessionDBID): indexToString(2),
},
}
)
// TestMigrateSessionIDIndex tests that the MigrateSessionIDIndex function
// correctly adds a new session-id index to the DB and also correctly updates
// the existing session bucket.
func TestMigrateSessionIDIndex(t *testing.T) {
t.Parallel()
tests := []struct {
name string
shouldFail bool
pre map[string]interface{}
postSessions map[string]interface{}
postIndex map[string]interface{}
}{
{
name: "migration ok",
shouldFail: false,
pre: pre,
postSessions: postSessions,
postIndex: postIndex,
},
{
name: "fail due to corrupt db",
shouldFail: true,
pre: preFailCorruptDB,
},
{
name: "no channel details",
shouldFail: false,
pre: nil,
postSessions: nil,
postIndex: nil,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// Before the migration we have a details bucket.
before := func(tx kvdb.RwTx) error {
return migtest.RestoreDB(
tx, cSessionBkt, test.pre,
)
}
// After the migration, we should have an untouched
// summary bucket and a new index bucket.
after := func(tx kvdb.RwTx) error {
// If the migration fails, the details bucket
// should be untouched.
if test.shouldFail {
if err := migtest.VerifyDB(
tx, cSessionBkt, test.pre,
); err != nil {
return err
}
return nil
}
// Else, we expect an updated summary bucket
// and a new index bucket.
err := migtest.VerifyDB(
tx, cSessionBkt, test.postSessions,
)
if err != nil {
return err
}
return migtest.VerifyDB(
tx, cSessionIDIndexBkt, test.postIndex,
)
}
migtest.ApplyMigration(
t, before, after, MigrateSessionIDIndex,
test.shouldFail,
)
})
}
}
func indexToString(id uint64) string {
var newIndex bytes.Buffer
err := tlv.WriteVarInt(&newIndex, id, &[8]byte{})
if err != nil {
panic(err)
}
return newIndex.String()
}
func sessionIDToString(id uint64) string {
var chanID SessionID
byteOrder.PutUint64(chanID[:], id)
return chanID.String()
}
+17
View File
@@ -0,0 +1,17 @@
package migration6
import (
"encoding/hex"
)
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
+14
View File
@@ -0,0 +1,14 @@
package migration6
import (
"github.com/btcsuite/btclog"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
+202
View File
@@ -0,0 +1,202 @@
package migration7
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// cSessionBkt is a top-level bucket storing:
// session-id => cSessionBody -> encoded ClientSessionBody
// => cSessionDBID -> db-assigned-id
// => cSessionCommits => seqnum -> encoded CommittedUpdate
// => cSessionAckRangeIndex => chan-id => acked-index-range
cSessionBkt = []byte("client-session-bucket")
// cChanDetailsBkt is a top-level bucket storing:
// channel-id => cChannelSummary -> encoded ClientChanSummary.
// => cChanDBID -> db-assigned-id
// => cChanSessions => db-session-id -> 1
cChanDetailsBkt = []byte("client-channel-detail-bucket")
// cChannelSummary is a sub-bucket of cChanDetailsBkt which stores the
// encoded body of ClientChanSummary.
cChannelSummary = []byte("client-channel-summary")
// cChanSessions is a sub-bucket of cChanDetailsBkt which stores:
// session-id -> 1
cChanSessions = []byte("client-channel-sessions")
// cSessionAckRangeIndex is a sub-bucket of cSessionBkt storing:
// chan-id => start -> end
cSessionAckRangeIndex = []byte("client-session-ack-range-index")
// cSessionDBID is a key used in the cSessionBkt to store the
// db-assigned-d of a session.
cSessionDBID = []byte("client-session-db-id")
// cChanIDIndexBkt is a top-level bucket storing:
// db-assigned-id -> channel-ID
cChanIDIndexBkt = []byte("client-channel-id-index")
// ErrUninitializedDB signals that top-level buckets for the database
// have not been initialized.
ErrUninitializedDB = errors.New("db not initialized")
// ErrCorruptClientSession signals that the client session's on-disk
// structure deviates from what is expected.
ErrCorruptClientSession = errors.New("client session corrupted")
// byteOrder is the default endianness used when serializing integers.
byteOrder = binary.BigEndian
)
// MigrateChannelToSessionIndex migrates the tower client DB to add an index
// from channel-to-session. This will make it easier in future to check which
// sessions have updates for which channels.
func MigrateChannelToSessionIndex(tx kvdb.RwTx) error {
log.Infof("Migrating the tower client DB to build a new " +
"channel-to-session index")
sessionsBkt := tx.ReadBucket(cSessionBkt)
if sessionsBkt == nil {
return ErrUninitializedDB
}
chanDetailsBkt := tx.ReadWriteBucket(cChanDetailsBkt)
if chanDetailsBkt == nil {
return ErrUninitializedDB
}
chanIDsBkt := tx.ReadBucket(cChanIDIndexBkt)
if chanIDsBkt == nil {
return ErrUninitializedDB
}
// First gather all the new channel-to-session pairs that we want to
// add.
index, err := collectIndex(sessionsBkt)
if err != nil {
return err
}
// Then persist those pairs to the db.
return persistIndex(chanDetailsBkt, chanIDsBkt, index)
}
// collectIndex iterates through all the sessions and uses the keys in the
// cSessionAckRangeIndex bucket to collect all the channels that the session
// has updates for. The function returns a map from channel ID to session ID
// (using the db-assigned IDs for both).
func collectIndex(sessionsBkt kvdb.RBucket) (map[uint64]map[uint64]bool,
error) {
index := make(map[uint64]map[uint64]bool)
err := sessionsBkt.ForEach(func(sessID, _ []byte) error {
sessionBkt := sessionsBkt.NestedReadBucket(sessID)
if sessionBkt == nil {
return ErrCorruptClientSession
}
ackedRanges := sessionBkt.NestedReadBucket(
cSessionAckRangeIndex,
)
if ackedRanges == nil {
return nil
}
sessDBIDBytes := sessionBkt.Get(cSessionDBID)
if sessDBIDBytes == nil {
return ErrCorruptClientSession
}
sessDBID, err := readUint64(sessDBIDBytes)
if err != nil {
return err
}
return ackedRanges.ForEach(func(dbChanIDBytes, _ []byte) error {
dbChanID, err := readUint64(dbChanIDBytes)
if err != nil {
return err
}
if _, ok := index[dbChanID]; !ok {
index[dbChanID] = make(map[uint64]bool)
}
index[dbChanID][sessDBID] = true
return nil
})
})
if err != nil {
return nil, err
}
return index, nil
}
// persistIndex adds the channel-to-session mapping in each channel's details
// bucket.
func persistIndex(chanDetailsBkt kvdb.RwBucket, chanIDsBkt kvdb.RBucket,
index map[uint64]map[uint64]bool) error {
for dbChanID, sessIDs := range index {
dbChanIDBytes, err := writeUint64(dbChanID)
if err != nil {
return err
}
realChanID := chanIDsBkt.Get(dbChanIDBytes)
chanBkt := chanDetailsBkt.NestedReadWriteBucket(realChanID)
if chanBkt == nil {
return fmt.Errorf("channel not found")
}
sessIDsBkt, err := chanBkt.CreateBucket(cChanSessions)
if err != nil {
return err
}
for id := range sessIDs {
sessID, err := writeUint64(id)
if err != nil {
return err
}
err = sessIDsBkt.Put(sessID, []byte{1})
if err != nil {
return err
}
}
}
return nil
}
func writeUint64(i uint64) ([]byte, error) {
var b bytes.Buffer
err := tlv.WriteVarInt(&b, i, &[8]byte{})
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
func readUint64(b []byte) (uint64, error) {
r := bytes.NewReader(b)
i, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return 0, err
}
return i, nil
}
@@ -0,0 +1,191 @@
package migration7
import (
"testing"
"github.com/lightningnetwork/lnd/channeldb/migtest"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// preDetails is the expected data of the channel details bucket before
// the migration.
preDetails = map[string]interface{}{
channelIDString(100): map[string]interface{}{
string(cChannelSummary): string([]byte{1, 2, 3}),
},
channelIDString(222): map[string]interface{}{
string(cChannelSummary): string([]byte{4, 5, 6}),
},
}
// preFailCorruptDB should fail the migration due to no channel summary
// being found for a given channel ID.
preFailCorruptDB = map[string]interface{}{
channelIDString(30): map[string]interface{}{},
}
// channelIDIndex is the data in the channelID index that is used to
// find the mapping between the db-assigned channel ID and the real
// channel ID.
channelIDIndex = map[string]interface{}{
uint64ToStr(10): channelIDString(100),
uint64ToStr(20): channelIDString(222),
}
// sessions is the expected data in the sessions bucket before and
// after the migration.
sessions = map[string]interface{}{
sessionIDString("1"): map[string]interface{}{
string(cSessionAckRangeIndex): map[string]interface{}{
uint64ToStr(10): map[string]interface{}{
uint64ToStr(30): uint64ToStr(32),
uint64ToStr(34): uint64ToStr(34),
},
uint64ToStr(20): map[string]interface{}{
uint64ToStr(30): uint64ToStr(30),
},
},
string(cSessionDBID): uint64ToStr(66),
},
sessionIDString("2"): map[string]interface{}{
string(cSessionAckRangeIndex): map[string]interface{}{
uint64ToStr(10): map[string]interface{}{
uint64ToStr(33): uint64ToStr(33),
},
},
string(cSessionDBID): uint64ToStr(77),
},
}
// postDetails is the expected data in the channel details bucket after
// the migration.
postDetails = map[string]interface{}{
channelIDString(100): map[string]interface{}{
string(cChannelSummary): string([]byte{1, 2, 3}),
string(cChanSessions): map[string]interface{}{
uint64ToStr(66): string([]byte{1}),
uint64ToStr(77): string([]byte{1}),
},
},
channelIDString(222): map[string]interface{}{
string(cChannelSummary): string([]byte{4, 5, 6}),
string(cChanSessions): map[string]interface{}{
uint64ToStr(66): string([]byte{1}),
},
},
}
)
// TestMigrateChannelToSessionIndex tests that the MigrateChannelToSessionIndex
// function correctly builds the new channel-to-sessionID index to the tower
// client DB.
func TestMigrateChannelToSessionIndex(t *testing.T) {
t.Parallel()
tests := []struct {
name string
shouldFail bool
preDetails map[string]interface{}
preSessions map[string]interface{}
preChanIndex map[string]interface{}
postDetails map[string]interface{}
}{
{
name: "migration ok",
shouldFail: false,
preDetails: preDetails,
preSessions: sessions,
preChanIndex: channelIDIndex,
postDetails: postDetails,
},
{
name: "fail due to corrupt db",
shouldFail: true,
preDetails: preFailCorruptDB,
preSessions: sessions,
},
{
name: "no sessions",
shouldFail: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// Before the migration we have a channel details
// bucket, a sessions bucket, a session ID index bucket
// and a channel ID index bucket.
before := func(tx kvdb.RwTx) error {
err := migtest.RestoreDB(
tx, cChanDetailsBkt, test.preDetails,
)
if err != nil {
return err
}
err = migtest.RestoreDB(
tx, cSessionBkt, test.preSessions,
)
if err != nil {
return err
}
return migtest.RestoreDB(
tx, cChanIDIndexBkt, test.preChanIndex,
)
}
after := func(tx kvdb.RwTx) error {
// If the migration fails, the details bucket
// should be untouched.
if test.shouldFail {
if err := migtest.VerifyDB(
tx, cChanDetailsBkt,
test.preDetails,
); err != nil {
return err
}
return nil
}
// Else, we expect an updated details bucket
// and a new index bucket.
return migtest.VerifyDB(
tx, cChanDetailsBkt, test.postDetails,
)
}
migtest.ApplyMigration(
t, before, after, MigrateChannelToSessionIndex,
test.shouldFail,
)
})
}
}
func sessionIDString(id string) string {
var sessID SessionID
copy(sessID[:], id)
return sessID.String()
}
func channelIDString(id uint64) string {
var chanID ChannelID
byteOrder.PutUint64(chanID[:], id)
return string(chanID[:])
}
func uint64ToStr(id uint64) string {
b, err := writeUint64(id)
if err != nil {
panic(err)
}
return string(b)
}
+29
View File
@@ -0,0 +1,29 @@
package migration7
import "encoding/hex"
// SessionIDSize is 33-bytes; it is a serialized, compressed public key.
const SessionIDSize = 33
// SessionID is created from the remote public key of a client, and serves as a
// unique identifier and authentication for sending state updates.
type SessionID [SessionIDSize]byte
// String returns a hex encoding of the session id.
func (s SessionID) String() string {
return hex.EncodeToString(s[:])
}
// ChannelID is a series of 32-bytes that uniquely identifies all channels
// within the network. The ChannelID is computed using the outpoint of the
// funding transaction (the txid, and output index). Given a funding output the
// ChannelID can be calculated by XOR'ing the big-endian serialization of the
// txid and the big-endian serialization of the output index, truncated to
// 2 bytes.
type ChannelID [32]byte
// String returns the string representation of the ChannelID. This is just the
// hex string encoding of the ChannelID itself.
func (c ChannelID) String() string {
return hex.EncodeToString(c[:])
}
+14
View File
@@ -0,0 +1,14 @@
package migration7
import (
"github.com/btcsuite/btclog"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
+8
View File
@@ -10,6 +10,8 @@ import (
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration3" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration3"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration4" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration4"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration5" "github.com/lightningnetwork/lnd/watchtower/wtdb/migration5"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration6"
"github.com/lightningnetwork/lnd/watchtower/wtdb/migration7"
) )
// txMigration is a function which takes a prior outdated version of the // txMigration is a function which takes a prior outdated version of the
@@ -59,6 +61,12 @@ var clientDBVersions = []version{
{ {
txMigration: migration5.MigrateCompleteTowerToSessionIndex, txMigration: migration5.MigrateCompleteTowerToSessionIndex,
}, },
{
txMigration: migration6.MigrateSessionIDIndex,
},
{
txMigration: migration7.MigrateChannelToSessionIndex,
},
} }
// getLatestDBVersion returns the last known database version. // getLatestDBVersion returns the last known database version.
+234 -24
View File
@@ -25,19 +25,26 @@ type rangeIndexArrayMap map[wtdb.SessionID]map[lnwire.ChannelID]*wtdb.RangeIndex
type rangeIndexKVStore map[wtdb.SessionID]map[lnwire.ChannelID]*mockKVStore type rangeIndexKVStore map[wtdb.SessionID]map[lnwire.ChannelID]*mockKVStore
type channel struct {
summary *wtdb.ClientChanSummary
closedHeight uint32
sessions map[wtdb.SessionID]bool
}
// ClientDB is a mock, in-memory database or testing the watchtower client // ClientDB is a mock, in-memory database or testing the watchtower client
// behavior. // behavior.
type ClientDB struct { type ClientDB struct {
nextTowerID uint64 // to be used atomically nextTowerID uint64 // to be used atomically
mu sync.Mutex mu sync.Mutex
summaries map[lnwire.ChannelID]wtdb.ClientChanSummary channels map[lnwire.ChannelID]*channel
activeSessions map[wtdb.SessionID]wtdb.ClientSession activeSessions map[wtdb.SessionID]wtdb.ClientSession
ackedUpdates rangeIndexArrayMap ackedUpdates rangeIndexArrayMap
persistedAckedUpdates rangeIndexKVStore persistedAckedUpdates rangeIndexKVStore
committedUpdates map[wtdb.SessionID][]wtdb.CommittedUpdate committedUpdates map[wtdb.SessionID][]wtdb.CommittedUpdate
towerIndex map[towerPK]wtdb.TowerID towerIndex map[towerPK]wtdb.TowerID
towers map[wtdb.TowerID]*wtdb.Tower towers map[wtdb.TowerID]*wtdb.Tower
closableSessions map[wtdb.SessionID]uint32
nextIndex uint32 nextIndex uint32
indexes map[keyIndexKey]uint32 indexes map[keyIndexKey]uint32
@@ -47,9 +54,7 @@ type ClientDB struct {
// NewClientDB initializes a new mock ClientDB. // NewClientDB initializes a new mock ClientDB.
func NewClientDB() *ClientDB { func NewClientDB() *ClientDB {
return &ClientDB{ return &ClientDB{
summaries: make( channels: make(map[lnwire.ChannelID]*channel),
map[lnwire.ChannelID]wtdb.ClientChanSummary,
),
activeSessions: make( activeSessions: make(
map[wtdb.SessionID]wtdb.ClientSession, map[wtdb.SessionID]wtdb.ClientSession,
), ),
@@ -58,10 +63,11 @@ func NewClientDB() *ClientDB {
committedUpdates: make( committedUpdates: make(
map[wtdb.SessionID][]wtdb.CommittedUpdate, map[wtdb.SessionID][]wtdb.CommittedUpdate,
), ),
towerIndex: make(map[towerPK]wtdb.TowerID), towerIndex: make(map[towerPK]wtdb.TowerID),
towers: make(map[wtdb.TowerID]*wtdb.Tower), towers: make(map[wtdb.TowerID]*wtdb.Tower),
indexes: make(map[keyIndexKey]uint32), indexes: make(map[keyIndexKey]uint32),
legacyIndexes: make(map[wtdb.TowerID]uint32), legacyIndexes: make(map[wtdb.TowerID]uint32),
closableSessions: make(map[wtdb.SessionID]uint32),
} }
} }
@@ -225,6 +231,7 @@ func (m *ClientDB) ListClientSessions(tower *wtdb.TowerID,
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
return m.listClientSessions(tower, opts...) return m.listClientSessions(tower, opts...)
} }
@@ -246,7 +253,12 @@ func (m *ClientDB) listClientSessions(tower *wtdb.TowerID,
if tower != nil && *tower != session.TowerID { if tower != nil && *tower != session.TowerID {
continue continue
} }
sessions[session.ID] = &session
if cfg.PreEvaluateFilterFn != nil &&
!cfg.PreEvaluateFilterFn(&session) {
continue
}
if cfg.PerMaxHeight != nil { if cfg.PerMaxHeight != nil {
for chanID, index := range m.ackedUpdates[session.ID] { for chanID, index := range m.ackedUpdates[session.ID] {
@@ -271,6 +283,14 @@ func (m *ClientDB) listClientSessions(tower *wtdb.TowerID,
cfg.PerCommittedUpdate(&session, &update) cfg.PerCommittedUpdate(&session, &update)
} }
} }
if cfg.PostEvaluateFilterFn != nil &&
!cfg.PostEvaluateFilterFn(&session) {
continue
}
sessions[session.ID] = &session
} }
return sessions, nil return sessions, nil
@@ -381,9 +401,10 @@ func (m *ClientDB) CreateClientSession(session *wtdb.ClientSession) error {
// particular tower id. The index is reserved for that tower until // particular tower id. The index is reserved for that tower until
// CreateClientSession is invoked for that tower and index, at which point a new // CreateClientSession is invoked for that tower and index, at which point a new
// index for that tower can be reserved. Multiple calls to this method before // index for that tower can be reserved. Multiple calls to this method before
// CreateClientSession is invoked should return the same index. // CreateClientSession is invoked should return the same index unless forceNext
func (m *ClientDB) NextSessionKeyIndex(towerID wtdb.TowerID, // is set to true.
blobType blob.Type) (uint32, error) { func (m *ClientDB) NextSessionKeyIndex(towerID wtdb.TowerID, blobType blob.Type,
forceNext bool) (uint32, error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
@@ -393,15 +414,24 @@ func (m *ClientDB) NextSessionKeyIndex(towerID wtdb.TowerID,
blobType: blobType, blobType: blobType,
} }
if index, err := m.getSessionKeyIndex(key); err == nil { if !forceNext {
return index, nil if index, err := m.getSessionKeyIndex(key); err == nil {
return index, nil
}
} }
m.nextIndex++ // By default, we use the next available bucket sequence as the key
index := m.nextIndex // index. But if forceNext is true, then it is assumed that some data
m.indexes[key] = index // loss occurred and so the sequence is incremented a by a jump of 1000
// so that we can arrive at a brand new key index quicker.
nextIndex := m.nextIndex + 1
if forceNext {
nextIndex = m.nextIndex + 1000
}
m.nextIndex = nextIndex
m.indexes[key] = nextIndex
return index, nil return nextIndex, nil
} }
func (m *ClientDB) getSessionKeyIndex(key keyIndexKey) (uint32, error) { func (m *ClientDB) getSessionKeyIndex(key keyIndexKey) (uint32, error) {
@@ -496,6 +526,13 @@ func (m *ClientDB) AckUpdate(id *wtdb.SessionID, seqNum,
continue continue
} }
// Add sessionID to channel.
channel, ok := m.channels[update.BackupID.ChanID]
if !ok {
return wtdb.ErrChannelNotRegistered
}
channel.sessions[*id] = true
// Remove the committed update from disk and mark the update as // Remove the committed update from disk and mark the update as
// acked. The tower last applied value is also recorded to send // acked. The tower last applied value is also recorded to send
// along with the next update. // along with the next update.
@@ -531,22 +568,192 @@ func (m *ClientDB) AckUpdate(id *wtdb.SessionID, seqNum,
return wtdb.ErrCommittedUpdateNotFound return wtdb.ErrCommittedUpdateNotFound
} }
// ListClosableSessions fetches and returns the IDs for all sessions marked as
// closable.
func (m *ClientDB) ListClosableSessions() (map[wtdb.SessionID]uint32, error) {
m.mu.Lock()
defer m.mu.Unlock()
cs := make(map[wtdb.SessionID]uint32, len(m.closableSessions))
for id, height := range m.closableSessions {
cs[id] = height
}
return cs, nil
}
// FetchChanSummaries loads a mapping from all registered channels to their // FetchChanSummaries loads a mapping from all registered channels to their
// channel summaries. // channel summaries. Only the channels that have not yet been marked as closed
// will be loaded.
func (m *ClientDB) FetchChanSummaries() (wtdb.ChannelSummaries, error) { func (m *ClientDB) FetchChanSummaries() (wtdb.ChannelSummaries, error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
summaries := make(map[lnwire.ChannelID]wtdb.ClientChanSummary) summaries := make(map[lnwire.ChannelID]wtdb.ClientChanSummary)
for chanID, summary := range m.summaries { for chanID, channel := range m.channels {
// Don't load the channel if it has been marked as closed.
if channel.closedHeight > 0 {
continue
}
summaries[chanID] = wtdb.ClientChanSummary{ summaries[chanID] = wtdb.ClientChanSummary{
SweepPkScript: cloneBytes(summary.SweepPkScript), SweepPkScript: cloneBytes(
channel.summary.SweepPkScript,
),
} }
} }
return summaries, nil return summaries, nil
} }
// MarkChannelClosed will mark a registered channel as closed by setting
// its closed-height as the given block height. It returns a list of
// session IDs for sessions that are now considered closable due to the
// close of this channel.
func (m *ClientDB) MarkChannelClosed(chanID lnwire.ChannelID,
blockHeight uint32) ([]wtdb.SessionID, error) {
m.mu.Lock()
defer m.mu.Unlock()
channel, ok := m.channels[chanID]
if !ok {
return nil, wtdb.ErrChannelNotRegistered
}
// If there are no sessions for this channel, the channel details can be
// deleted.
if len(channel.sessions) == 0 {
delete(m.channels, chanID)
return nil, nil
}
// Mark the channel as closed.
channel.closedHeight = blockHeight
// Now iterate through all the sessions of the channel to check if any
// of them are closeable.
var closableSessions []wtdb.SessionID
for sessID := range channel.sessions {
isClosable, err := m.isSessionClosable(sessID)
if err != nil {
return nil, err
}
if !isClosable {
continue
}
closableSessions = append(closableSessions, sessID)
// Add session to "closableSessions" list and add the block
// height that this last channel was closed in. This will be
// used in future to determine when we should delete the
// session.
m.closableSessions[sessID] = blockHeight
}
return closableSessions, nil
}
// isSessionClosable returns true if a session is considered closable. A session
// is considered closable only if:
// 1) It has no un-acked updates
// 2) It is exhausted (ie it cant accept any more updates)
// 3) All the channels that it has acked-updates for are closed.
func (m *ClientDB) isSessionClosable(id wtdb.SessionID) (bool, error) {
// The session is not closable if it has un-acked updates.
if len(m.committedUpdates[id]) > 0 {
return false, nil
}
sess, ok := m.activeSessions[id]
if !ok {
return false, wtdb.ErrClientSessionNotFound
}
// The session is not closable if it is not yet exhausted.
if sess.SeqNum != sess.Policy.MaxUpdates {
return false, nil
}
// Iterate over each of the channels that the session has acked-updates
// for. If any of those channels are not closed, then the session is
// not yet closable.
for chanID := range m.ackedUpdates[id] {
channel, ok := m.channels[chanID]
if !ok {
continue
}
// Channel is not yet closed, and so we can not yet delete the
// session.
if channel.closedHeight == 0 {
return false, nil
}
}
return true, nil
}
// GetClientSession loads the ClientSession with the given ID from the DB.
func (m *ClientDB) GetClientSession(id wtdb.SessionID,
opts ...wtdb.ClientSessionListOption) (*wtdb.ClientSession, error) {
cfg := wtdb.NewClientSessionCfg()
for _, o := range opts {
o(cfg)
}
session, ok := m.activeSessions[id]
if !ok {
return nil, wtdb.ErrClientSessionNotFound
}
if cfg.PerMaxHeight != nil {
for chanID, index := range m.ackedUpdates[session.ID] {
cfg.PerMaxHeight(&session, chanID, index.MaxHeight())
}
}
if cfg.PerCommittedUpdate != nil {
for _, update := range m.committedUpdates[session.ID] {
update := update
cfg.PerCommittedUpdate(&session, &update)
}
}
return &session, nil
}
// DeleteSession can be called when a session should be deleted from the DB.
// All references to the session will also be deleted from the DB. Note that a
// session will only be deleted if it is considered closable.
func (m *ClientDB) DeleteSession(id wtdb.SessionID) error {
m.mu.Lock()
defer m.mu.Unlock()
_, ok := m.closableSessions[id]
if !ok {
return wtdb.ErrSessionNotClosable
}
// For each of the channels, delete the session ID entry.
for chanID := range m.ackedUpdates[id] {
c, ok := m.channels[chanID]
if !ok {
return wtdb.ErrChannelNotRegistered
}
delete(c.sessions, id)
}
delete(m.closableSessions, id)
delete(m.activeSessions, id)
return nil
}
// RegisterChannel registers a channel for use within the client database. For // RegisterChannel registers a channel for use within the client database. For
// now, all that is stored in the channel summary is the sweep pkscript that // now, all that is stored in the channel summary is the sweep pkscript that
// we'd like any tower sweeps to pay into. In the future, this will be extended // we'd like any tower sweeps to pay into. In the future, this will be extended
@@ -558,12 +765,15 @@ func (m *ClientDB) RegisterChannel(chanID lnwire.ChannelID,
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
if _, ok := m.summaries[chanID]; ok { if _, ok := m.channels[chanID]; ok {
return wtdb.ErrChannelAlreadyRegistered return wtdb.ErrChannelAlreadyRegistered
} }
m.summaries[chanID] = wtdb.ClientChanSummary{ m.channels[chanID] = &channel{
SweepPkScript: cloneBytes(sweepPkScript), summary: &wtdb.ClientChanSummary{
SweepPkScript: cloneBytes(sweepPkScript),
},
sessions: make(map[wtdb.SessionID]bool),
} }
return nil return nil