mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-27 22:21:18 +02:00
Merge pull request #4782 from cfromknecht/anchor-wtserver
watchtower: anchor channel support
This commit is contained in:
@@ -138,8 +138,9 @@ func (t Type) String() string {
|
||||
// supportedTypes is the set of all configurations known to be supported by the
|
||||
// package.
|
||||
var supportedTypes = map[Type]struct{}{
|
||||
TypeAltruistCommit: {},
|
||||
TypeRewardCommit: {},
|
||||
TypeAltruistCommit: {},
|
||||
TypeRewardCommit: {},
|
||||
TypeAltruistAnchorCommit: {},
|
||||
}
|
||||
|
||||
// IsSupportedType returns true if the given type is supported by the package.
|
||||
|
@@ -123,6 +123,12 @@ func TestSupportedTypes(t *testing.T) {
|
||||
t.Fatalf("default type %s is not supported", blob.TypeAltruistCommit)
|
||||
}
|
||||
|
||||
// Assert that the altruist anchor commit types are supported.
|
||||
if !blob.IsSupportedType(blob.TypeAltruistAnchorCommit) {
|
||||
t.Fatalf("default type %s is not supported",
|
||||
blob.TypeAltruistAnchorCommit)
|
||||
}
|
||||
|
||||
// Assert that all claimed supported types are actually supported.
|
||||
for _, supType := range blob.SupportedTypes() {
|
||||
if blob.IsSupportedType(supType) {
|
||||
|
@@ -1,12 +1,15 @@
|
||||
package wtclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/txsort"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
@@ -36,6 +39,7 @@ import (
|
||||
type backupTask struct {
|
||||
id wtdb.BackupID
|
||||
breachInfo *lnwallet.BreachRetribution
|
||||
chanType channeldb.ChannelType
|
||||
|
||||
// state-dependent variables
|
||||
|
||||
@@ -54,7 +58,7 @@ type backupTask struct {
|
||||
// variables.
|
||||
func newBackupTask(chanID *lnwire.ChannelID,
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
sweepPkScript []byte, isTweakless bool) *backupTask {
|
||||
sweepPkScript []byte, chanType channeldb.ChannelType) *backupTask {
|
||||
|
||||
// Parse the non-dust outputs from the breach transaction,
|
||||
// simultaneously computing the total amount contained in the inputs
|
||||
@@ -85,17 +89,35 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
|
||||
}
|
||||
if breachInfo.LocalOutputSignDesc != nil {
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if isTweakless {
|
||||
var witnessType input.WitnessType
|
||||
switch {
|
||||
case chanType.HasAnchors():
|
||||
witnessType = input.CommitmentToRemoteConfirmed
|
||||
case chanType.IsTweakless():
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
default:
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
// Anchor channels have a CSV-encumbered to-remote output. We'll
|
||||
// construct a CSV input in that case and assign the proper CSV
|
||||
// delay of 1, otherwise we fallback to the a regular P2WKH
|
||||
// to-remote output for tweaked or tweakless channels.
|
||||
if chanType.HasAnchors() {
|
||||
toRemoteInput = input.NewCsvInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0, 1,
|
||||
)
|
||||
} else {
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
|
||||
}
|
||||
@@ -106,6 +128,7 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
CommitHeight: breachInfo.RevokedStateNum,
|
||||
},
|
||||
breachInfo: breachInfo,
|
||||
chanType: chanType,
|
||||
toLocalInput: toLocalInput,
|
||||
toRemoteInput: toRemoteInput,
|
||||
totalAmt: btcutil.Amount(totalAmt),
|
||||
@@ -145,13 +168,28 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
|
||||
// underestimate the size by one byte. The diferrence in weight
|
||||
// can cause different output values on the sweep transaction,
|
||||
// so we mimic the original bug and create signatures using the
|
||||
// original weight estimate.
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize - 1,
|
||||
)
|
||||
// original weight estimate. For anchor channels we'll go ahead
|
||||
// an use the correct penalty witness when signing our justice
|
||||
// transactions.
|
||||
if t.chanType.HasAnchors() {
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize,
|
||||
)
|
||||
} else {
|
||||
weightEstimate.AddWitnessInput(
|
||||
input.ToLocalPenaltyWitnessSize - 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
if t.toRemoteInput != nil {
|
||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||
// Legacy channels (both tweaked and non-tweaked) spend from
|
||||
// P2WKH output. Anchor channels spend a to-remote confirmed
|
||||
// P2WSH output.
|
||||
if t.chanType.HasAnchors() {
|
||||
weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize)
|
||||
} else {
|
||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||
}
|
||||
}
|
||||
|
||||
// All justice transactions have a p2wkh output paying to the victim.
|
||||
@@ -163,6 +201,12 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error {
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
}
|
||||
|
||||
if t.chanType.HasAnchors() != session.Policy.IsAnchorChannel() {
|
||||
log.Criticalf("Invalid task (has_anchors=%t) for session "+
|
||||
"(has_anchors=%t)", t.chanType.HasAnchors(),
|
||||
session.Policy.IsAnchorChannel())
|
||||
}
|
||||
|
||||
// Now, compute the output values depending on whether FlagReward is set
|
||||
// in the current session's policy.
|
||||
outputs, err := session.Policy.ComputeJusticeTxOuts(
|
||||
@@ -219,9 +263,10 @@ func (t *backupTask) craftSessionPayload(
|
||||
// information. This will either be contain both the to-local and
|
||||
// to-remote outputs, or only be the to-local output.
|
||||
inputs := t.inputs()
|
||||
for prevOutPoint := range inputs {
|
||||
for prevOutPoint, input := range inputs {
|
||||
justiceTxn.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: prevOutPoint,
|
||||
Sequence: input.BlocksToMaturity(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,7 +333,12 @@ func (t *backupTask) craftSessionPayload(
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
fallthrough
|
||||
case input.CommitmentToRemoteConfirmed:
|
||||
copy(justiceKit.CommitToRemoteSig[:], signature[:])
|
||||
default:
|
||||
return hint, nil, fmt.Errorf("invalid witness type: %v",
|
||||
inp.WitnessType())
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
@@ -74,7 +75,7 @@ type backupTaskTest struct {
|
||||
bindErr error
|
||||
expSweepScript []byte
|
||||
signer input.Signer
|
||||
tweakless bool
|
||||
chanType channeldb.ChannelType
|
||||
}
|
||||
|
||||
// genTaskTest creates a instance of a backupTaskTest using the passed
|
||||
@@ -92,7 +93,13 @@ func genTaskTest(
|
||||
expSweepAmt int64,
|
||||
expRewardAmt int64,
|
||||
bindErr error,
|
||||
tweakless bool) backupTaskTest {
|
||||
chanType channeldb.ChannelType) backupTaskTest {
|
||||
|
||||
// Set the anchor flag in the blob type if the session needs to support
|
||||
// anchor channels.
|
||||
if chanType.HasAnchors() {
|
||||
blobType |= blob.Type(blob.FlagAnchorChannel)
|
||||
}
|
||||
|
||||
// Parse the key pairs for all keys used in the test.
|
||||
revSK, revPK := btcec.PrivKeyFromBytes(
|
||||
@@ -192,17 +199,31 @@ func genTaskTest(
|
||||
Index: index,
|
||||
}
|
||||
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if tweakless {
|
||||
var witnessType input.WitnessType
|
||||
switch {
|
||||
case chanType.HasAnchors():
|
||||
witnessType = input.CommitmentToRemoteConfirmed
|
||||
case chanType.IsTweakless():
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
default:
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
if chanType.HasAnchors() {
|
||||
toRemoteInput = input.NewCsvInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0, 1,
|
||||
)
|
||||
} else {
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return backupTaskTest{
|
||||
@@ -227,7 +248,7 @@ func genTaskTest(
|
||||
bindErr: bindErr,
|
||||
expSweepScript: makeAddrSlice(22),
|
||||
signer: signer,
|
||||
tweakless: tweakless,
|
||||
chanType: chanType,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,60 +274,97 @@ var (
|
||||
func TestBackupTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
chanTypes := []channeldb.ChannelType{
|
||||
channeldb.SingleFunderBit,
|
||||
channeldb.SingleFunderTweaklessBit,
|
||||
channeldb.AnchorOutputsBit,
|
||||
}
|
||||
|
||||
var backupTaskTests []backupTaskTest
|
||||
for _, tweakless := range []bool{true, false} {
|
||||
for _, chanType := range chanTypes {
|
||||
// Depending on whether the test is for anchor channels or
|
||||
// legacy (tweaked and non-tweaked) channels, adjust the
|
||||
// expected sweep amount to accommodate. These are different for
|
||||
// several reasons:
|
||||
// - anchor to-remote outputs require a P2WSH sweep rather
|
||||
// than a P2WKH sweep.
|
||||
// - the to-local weight estimate fixes an off-by-one.
|
||||
// In tests related to the dust threshold, the size difference
|
||||
// between the channel types makes it so that the threshold fee
|
||||
// rate is slightly lower (since the transactions are heavier).
|
||||
var (
|
||||
expSweepCommitNoRewardBoth int64 = 299241
|
||||
expSweepCommitNoRewardLocal int64 = 199514
|
||||
expSweepCommitNoRewardRemote int64 = 99561
|
||||
expSweepCommitRewardBoth int64 = 296117
|
||||
expSweepCommitRewardLocal int64 = 197390
|
||||
expSweepCommitRewardRemote int64 = 98437
|
||||
sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500
|
||||
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175000
|
||||
)
|
||||
if chanType.HasAnchors() {
|
||||
expSweepCommitNoRewardBoth = 299236
|
||||
expSweepCommitNoRewardLocal = 199513
|
||||
expSweepCommitNoRewardRemote = 99557
|
||||
expSweepCommitRewardBoth = 296112
|
||||
expSweepCommitRewardLocal = 197389
|
||||
expSweepCommitRewardRemote = 98433
|
||||
sweepFeeRateNoRewardRemoteDust = 225000
|
||||
sweepFeeRateRewardRemoteDust = 173750
|
||||
}
|
||||
|
||||
backupTaskTests = append(backupTaskTests, []backupTaskTest{
|
||||
genTaskTest(
|
||||
"commit no-reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
299241, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardBoth, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-local output only",
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
199514, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardLocal, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
99561, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
expSweepCommitNoRewardRemote, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, to-remote output only, creates dust",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
227500, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
sweepFeeRateNoRewardRemoteDust, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate exceeds inputs",
|
||||
@@ -319,7 +377,7 @@ func TestBackupTask(t *testing.T) {
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate of 0 creates dust",
|
||||
@@ -332,59 +390,59 @@ func TestBackupTask(t *testing.T) {
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
296117, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardBoth, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-local output only",
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
197390, // expSweepAmt
|
||||
2000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
1000, // stateNum
|
||||
200000, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardLocal, // expSweepAmt
|
||||
2000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
98437, // expSweepAmt
|
||||
1000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
expSweepCommitRewardRemote, // expSweepAmt
|
||||
1000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, to-remote output only, creates dust",
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
175000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
1, // stateNum
|
||||
0, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
sweepFeeRateRewardRemoteDust, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate exceeds inputs",
|
||||
@@ -397,7 +455,7 @@ func TestBackupTask(t *testing.T) {
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
chanType,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate of 0 creates dust",
|
||||
@@ -410,7 +468,7 @@ func TestBackupTask(t *testing.T) {
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
chanType,
|
||||
),
|
||||
}...)
|
||||
}
|
||||
@@ -430,7 +488,7 @@ func testBackupTask(t *testing.T, test backupTaskTest) {
|
||||
// Create a new backupTask from the channel id and breach info.
|
||||
task := newBackupTask(
|
||||
&test.chanID, test.breachInfo, test.expSweepScript,
|
||||
test.tweakless,
|
||||
test.chanType,
|
||||
)
|
||||
|
||||
// Assert that all parameters set during initialization are properly
|
||||
|
@@ -10,6 +10,9 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
@@ -40,13 +43,14 @@ const (
|
||||
DefaultForceQuitDelay = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
// activeSessionFilter is a filter that ignored any sessions which are
|
||||
// not active.
|
||||
activeSessionFilter = func(s *wtdb.ClientSession) bool {
|
||||
return s.Status == wtdb.CSessionActive
|
||||
// genActiveSessionFilter generates a filter that selects active sessions that
|
||||
// also match the desired channel type, either legacy or anchor.
|
||||
func genActiveSessionFilter(anchor bool) func(*wtdb.ClientSession) bool {
|
||||
return func(s *wtdb.ClientSession) bool {
|
||||
return s.Status == wtdb.CSessionActive &&
|
||||
anchor == s.Policy.IsAnchorChannel()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// RegisteredTower encompasses information about a registered watchtower with
|
||||
// the client.
|
||||
@@ -103,7 +107,8 @@ type Client interface {
|
||||
// negotiated policy. If the channel we're trying to back up doesn't
|
||||
// have a tweak for the remote party's output, then isTweakless should
|
||||
// be true.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution, bool) error
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution,
|
||||
channeldb.ChannelType) error
|
||||
|
||||
// Start initializes the watchtower client, allowing it process requests
|
||||
// to backup revoked channel states.
|
||||
@@ -228,6 +233,8 @@ type TowerClient struct {
|
||||
|
||||
cfg *Config
|
||||
|
||||
log btclog.Logger
|
||||
|
||||
pipeline *taskPipeline
|
||||
|
||||
negotiator SessionNegotiator
|
||||
@@ -274,10 +281,18 @@ func New(config *Config) (*TowerClient, error) {
|
||||
cfg.WriteTimeout = DefaultWriteTimeout
|
||||
}
|
||||
|
||||
prefix := "(legacy)"
|
||||
if cfg.Policy.IsAnchorChannel() {
|
||||
prefix = "(anchor)"
|
||||
}
|
||||
plog := build.NewPrefixLog(prefix, log)
|
||||
|
||||
// Next, load all candidate sessions and towers from the database into
|
||||
// the client. We will use any of these session 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)
|
||||
candidateSessions, err := getClientSessions(
|
||||
cfg.DB, cfg.SecretKeyRing, nil, activeSessionFilter,
|
||||
)
|
||||
@@ -287,7 +302,7 @@ func New(config *Config) (*TowerClient, error) {
|
||||
|
||||
var candidateTowers []*wtdb.Tower
|
||||
for _, s := range candidateSessions {
|
||||
log.Infof("Using private watchtower %s, offering policy %s",
|
||||
plog.Infof("Using private watchtower %s, offering policy %s",
|
||||
s.Tower, cfg.Policy)
|
||||
candidateTowers = append(candidateTowers, s.Tower)
|
||||
}
|
||||
@@ -301,6 +316,7 @@ func New(config *Config) (*TowerClient, error) {
|
||||
|
||||
c := &TowerClient{
|
||||
cfg: cfg,
|
||||
log: plog,
|
||||
pipeline: newTaskPipeline(),
|
||||
candidateTowers: newTowerListIterator(candidateTowers...),
|
||||
candidateSessions: candidateSessions,
|
||||
@@ -422,7 +438,7 @@ func (c *TowerClient) buildHighestCommitHeights() {
|
||||
func (c *TowerClient) Start() error {
|
||||
var err error
|
||||
c.started.Do(func() {
|
||||
log.Infof("Starting watchtower client")
|
||||
c.log.Infof("Starting watchtower client")
|
||||
|
||||
// First, restart a session queue for any sessions that have
|
||||
// committed but unacked state updates. This ensures that these
|
||||
@@ -430,7 +446,7 @@ func (c *TowerClient) Start() error {
|
||||
// restart.
|
||||
for _, session := range c.candidateSessions {
|
||||
if len(session.CommittedUpdates) > 0 {
|
||||
log.Infof("Starting session=%s to process "+
|
||||
c.log.Infof("Starting session=%s to process "+
|
||||
"%d committed backups", session.ID,
|
||||
len(session.CommittedUpdates))
|
||||
c.initActiveQueue(session)
|
||||
@@ -460,7 +476,7 @@ func (c *TowerClient) Start() error {
|
||||
// Stop idempotently initiates a graceful shutdown of the watchtower client.
|
||||
func (c *TowerClient) Stop() error {
|
||||
c.stopped.Do(func() {
|
||||
log.Debugf("Stopping watchtower client")
|
||||
c.log.Debugf("Stopping watchtower client")
|
||||
|
||||
// 1. To ensure we don't hang forever on shutdown due to
|
||||
// unintended failures, we'll delay a call to force quit the
|
||||
@@ -493,7 +509,7 @@ func (c *TowerClient) Stop() error {
|
||||
// queues, we no longer need to negotiate sessions.
|
||||
c.negotiator.Stop()
|
||||
|
||||
log.Debugf("Waiting for active session queues to finish "+
|
||||
c.log.Debugf("Waiting for active session queues to finish "+
|
||||
"draining, stats: %s", c.stats)
|
||||
|
||||
// 5. Shutdown all active session queues in parallel. These will
|
||||
@@ -509,7 +525,7 @@ func (c *TowerClient) Stop() error {
|
||||
default:
|
||||
}
|
||||
|
||||
log.Debugf("Client successfully stopped, stats: %s", c.stats)
|
||||
c.log.Debugf("Client successfully stopped, stats: %s", c.stats)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -518,7 +534,7 @@ func (c *TowerClient) Stop() error {
|
||||
// client. This should only be executed if Stop is unable to exit cleanly.
|
||||
func (c *TowerClient) ForceQuit() {
|
||||
c.forced.Do(func() {
|
||||
log.Infof("Force quitting watchtower client")
|
||||
c.log.Infof("Force quitting watchtower client")
|
||||
|
||||
// 1. Shutdown the backup queue, which will prevent any further
|
||||
// updates from being accepted. In practice, the links should be
|
||||
@@ -543,7 +559,7 @@ func (c *TowerClient) ForceQuit() {
|
||||
return s.ForceQuit
|
||||
})
|
||||
|
||||
log.Infof("Watchtower client unclean shutdown complete, "+
|
||||
c.log.Infof("Watchtower client unclean shutdown complete, "+
|
||||
"stats: %s", c.stats)
|
||||
})
|
||||
}
|
||||
@@ -592,7 +608,8 @@ func (c *TowerClient) RegisterChannel(chanID lnwire.ChannelID) error {
|
||||
// - breached outputs contain too little value to sweep at the target sweep fee
|
||||
// rate.
|
||||
func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
breachInfo *lnwallet.BreachRetribution, isTweakless bool) error {
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
chanType channeldb.ChannelType) error {
|
||||
|
||||
// Retrieve the cached sweep pkscript used for this channel.
|
||||
c.backupMu.Lock()
|
||||
@@ -606,7 +623,7 @@ func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
height, ok := c.chanCommitHeights[*chanID]
|
||||
if ok && breachInfo.RevokedStateNum <= height {
|
||||
c.backupMu.Unlock()
|
||||
log.Debugf("Ignoring duplicate backup for chanid=%v at height=%d",
|
||||
c.log.Debugf("Ignoring duplicate backup for chanid=%v at height=%d",
|
||||
chanID, breachInfo.RevokedStateNum)
|
||||
return nil
|
||||
}
|
||||
@@ -618,7 +635,7 @@ func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
c.backupMu.Unlock()
|
||||
|
||||
task := newBackupTask(
|
||||
chanID, breachInfo, summary.SweepPkScript, isTweakless,
|
||||
chanID, breachInfo, summary.SweepPkScript, chanType,
|
||||
)
|
||||
|
||||
return c.pipeline.QueueBackupTask(task)
|
||||
@@ -669,15 +686,15 @@ func (c *TowerClient) nextSessionQueue() *sessionQueue {
|
||||
func (c *TowerClient) backupDispatcher() {
|
||||
defer c.wg.Done()
|
||||
|
||||
log.Tracef("Starting backup dispatcher")
|
||||
defer log.Tracef("Stopping backup dispatcher")
|
||||
c.log.Tracef("Starting backup dispatcher")
|
||||
defer c.log.Tracef("Stopping backup dispatcher")
|
||||
|
||||
for {
|
||||
switch {
|
||||
|
||||
// No active session queue and no additional sessions.
|
||||
case c.sessionQueue == nil && len(c.candidateSessions) == 0:
|
||||
log.Infof("Requesting new session.")
|
||||
c.log.Infof("Requesting new session.")
|
||||
|
||||
// Immediately request a new session.
|
||||
c.negotiator.RequestSession()
|
||||
@@ -688,7 +705,7 @@ func (c *TowerClient) backupDispatcher() {
|
||||
awaitSession:
|
||||
select {
|
||||
case session := <-c.negotiator.NewSessions():
|
||||
log.Infof("Acquired new session with id=%s",
|
||||
c.log.Infof("Acquired new session with id=%s",
|
||||
session.ID)
|
||||
c.candidateSessions[session.ID] = session
|
||||
c.stats.sessionAcquired()
|
||||
@@ -698,7 +715,7 @@ func (c *TowerClient) backupDispatcher() {
|
||||
continue
|
||||
|
||||
case <-c.statTicker.C:
|
||||
log.Infof("Client stats: %s", c.stats)
|
||||
c.log.Infof("Client stats: %s", c.stats)
|
||||
|
||||
// A new tower has been requested to be added. We'll
|
||||
// update our persisted and in-memory state and consider
|
||||
@@ -732,7 +749,7 @@ func (c *TowerClient) backupDispatcher() {
|
||||
// backup tasks.
|
||||
c.sessionQueue = c.nextSessionQueue()
|
||||
if c.sessionQueue != nil {
|
||||
log.Debugf("Loaded next candidate session "+
|
||||
c.log.Debugf("Loaded next candidate session "+
|
||||
"queue id=%s", c.sessionQueue.ID())
|
||||
}
|
||||
|
||||
@@ -759,13 +776,13 @@ func (c *TowerClient) backupDispatcher() {
|
||||
// we can request new sessions before the session is
|
||||
// fully empty, which this case would handle.
|
||||
case session := <-c.negotiator.NewSessions():
|
||||
log.Warnf("Acquired new session with id=%s "+
|
||||
c.log.Warnf("Acquired new session with id=%s "+
|
||||
"while processing tasks", session.ID)
|
||||
c.candidateSessions[session.ID] = session
|
||||
c.stats.sessionAcquired()
|
||||
|
||||
case <-c.statTicker.C:
|
||||
log.Infof("Client stats: %s", c.stats)
|
||||
c.log.Infof("Client stats: %s", c.stats)
|
||||
|
||||
// Process each backup task serially from the queue of
|
||||
// revoked states.
|
||||
@@ -776,7 +793,7 @@ func (c *TowerClient) backupDispatcher() {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Processing %v", task.id)
|
||||
c.log.Debugf("Processing %v", task.id)
|
||||
|
||||
c.stats.taskReceived()
|
||||
c.processTask(task)
|
||||
@@ -821,7 +838,7 @@ func (c *TowerClient) processTask(task *backupTask) {
|
||||
// sessionQueue will be removed if accepting the task left the sessionQueue in
|
||||
// an exhausted state.
|
||||
func (c *TowerClient) taskAccepted(task *backupTask, newStatus reserveStatus) {
|
||||
log.Infof("Queued %v successfully for session %v",
|
||||
c.log.Infof("Queued %v successfully for session %v",
|
||||
task.id, c.sessionQueue.ID())
|
||||
|
||||
c.stats.taskAccepted()
|
||||
@@ -840,7 +857,7 @@ func (c *TowerClient) taskAccepted(task *backupTask, newStatus reserveStatus) {
|
||||
case reserveExhausted:
|
||||
c.stats.sessionExhausted()
|
||||
|
||||
log.Debugf("Session %s exhausted", c.sessionQueue.ID())
|
||||
c.log.Debugf("Session %s exhausted", c.sessionQueue.ID())
|
||||
|
||||
// This task left the session exhausted, set it to nil and
|
||||
// proceed to the next loop so we can consume another
|
||||
@@ -863,13 +880,13 @@ func (c *TowerClient) taskRejected(task *backupTask, curStatus reserveStatus) {
|
||||
case reserveAvailable:
|
||||
c.stats.taskIneligible()
|
||||
|
||||
log.Infof("Ignoring ineligible %v", task.id)
|
||||
c.log.Infof("Ignoring ineligible %v", task.id)
|
||||
|
||||
err := c.cfg.DB.MarkBackupIneligible(
|
||||
task.id.ChanID, task.id.CommitHeight,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to mark %v ineligible: %v",
|
||||
c.log.Errorf("Unable to mark %v ineligible: %v",
|
||||
task.id, err)
|
||||
|
||||
// It is safe to not handle this error, even if we could
|
||||
@@ -889,7 +906,7 @@ func (c *TowerClient) taskRejected(task *backupTask, curStatus reserveStatus) {
|
||||
case reserveExhausted:
|
||||
c.stats.sessionExhausted()
|
||||
|
||||
log.Debugf("Session %v exhausted, %v queued for next session",
|
||||
c.log.Debugf("Session %v exhausted, %v queued for next session",
|
||||
c.sessionQueue.ID(), task.id)
|
||||
|
||||
// Cache the task that we pulled off, so that we can process it
|
||||
@@ -918,7 +935,7 @@ func (c *TowerClient) readMessage(peer wtserver.Peer) (wtwire.Message, error) {
|
||||
err := peer.SetReadDeadline(time.Now().Add(c.cfg.ReadTimeout))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to set read deadline: %v", err)
|
||||
log.Errorf("Unable to read msg: %v", err)
|
||||
c.log.Errorf("Unable to read msg: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -926,7 +943,7 @@ func (c *TowerClient) readMessage(peer wtserver.Peer) (wtwire.Message, error) {
|
||||
rawMsg, err := peer.ReadNextMessage()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to read message: %v", err)
|
||||
log.Errorf("Unable to read msg: %v", err)
|
||||
c.log.Errorf("Unable to read msg: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -936,11 +953,11 @@ func (c *TowerClient) readMessage(peer wtserver.Peer) (wtwire.Message, error) {
|
||||
msg, err := wtwire.ReadMessage(msgReader, 0)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to parse message: %v", err)
|
||||
log.Errorf("Unable to read msg: %v", err)
|
||||
c.log.Errorf("Unable to read msg: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logMessage(peer, msg, true)
|
||||
c.logMessage(peer, msg, true)
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@@ -953,7 +970,7 @@ func (c *TowerClient) sendMessage(peer wtserver.Peer, msg wtwire.Message) error
|
||||
_, err := wtwire.WriteMessage(&b, msg, 0)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to encode msg: %v", err)
|
||||
log.Errorf("Unable to send msg: %v", err)
|
||||
c.log.Errorf("Unable to send msg: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -962,16 +979,16 @@ func (c *TowerClient) sendMessage(peer wtserver.Peer, msg wtwire.Message) error
|
||||
err = peer.SetWriteDeadline(time.Now().Add(c.cfg.WriteTimeout))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to set write deadline: %v", err)
|
||||
log.Errorf("Unable to send msg: %v", err)
|
||||
c.log.Errorf("Unable to send msg: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logMessage(peer, msg, false)
|
||||
c.logMessage(peer, msg, false)
|
||||
|
||||
// Write out the full message to the remote peer.
|
||||
_, err = peer.Write(b.Bytes())
|
||||
if err != nil {
|
||||
log.Errorf("Unable to send msg: %v", err)
|
||||
c.log.Errorf("Unable to send msg: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1065,6 +1082,8 @@ func (c *TowerClient) handleNewTower(msg *newTowerMsg) error {
|
||||
c.candidateTowers.AddCandidate(tower)
|
||||
|
||||
// Include all of its corresponding sessions to our set of candidates.
|
||||
isAnchorClient := c.cfg.Policy.IsAnchorChannel()
|
||||
activeSessionFilter := genActiveSessionFilter(isAnchorClient)
|
||||
sessions, err := getClientSessions(
|
||||
c.cfg.DB, c.cfg.SecretKeyRing, &tower.ID, activeSessionFilter,
|
||||
)
|
||||
@@ -1232,7 +1251,9 @@ func (c *TowerClient) Policy() wtpolicy.Policy {
|
||||
// logMessage writes information about a message received from a remote peer,
|
||||
// using directional prepositions to signal whether the message was sent or
|
||||
// received.
|
||||
func logMessage(peer wtserver.Peer, msg wtwire.Message, read bool) {
|
||||
func (c *TowerClient) logMessage(
|
||||
peer wtserver.Peer, msg wtwire.Message, read bool) {
|
||||
|
||||
var action = "Received"
|
||||
var preposition = "from"
|
||||
if !read {
|
||||
@@ -1245,7 +1266,7 @@ func logMessage(peer wtserver.Peer, msg wtwire.Message, read bool) {
|
||||
summary = "(" + summary + ")"
|
||||
}
|
||||
|
||||
log.Debugf("%s %s%v %s %x@%s", action, msg.MsgType(), summary,
|
||||
c.log.Debugf("%s %s%v %s %x@%s", action, msg.MsgType(), summary,
|
||||
preposition, peer.RemotePub().SerializeCompressed(),
|
||||
peer.RemoteAddr())
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
@@ -633,7 +634,7 @@ func (h *testHarness) backupState(id, i uint64, expErr error) {
|
||||
_, retribution := h.channel(id).getState(i)
|
||||
|
||||
chanID := chanIDFromInt(id)
|
||||
err := h.client.BackupState(&chanID, retribution, false)
|
||||
err := h.client.BackupState(&chanID, retribution, channeldb.SingleFunderBit)
|
||||
if err != expErr {
|
||||
h.t.Fatalf("back error mismatch, want: %v, got: %v",
|
||||
expErr, err)
|
||||
|
@@ -112,8 +112,19 @@ var _ SessionNegotiator = (*sessionNegotiator)(nil)
|
||||
|
||||
// newSessionNegotiator initializes a fresh sessionNegotiator instance.
|
||||
func newSessionNegotiator(cfg *NegotiatorConfig) *sessionNegotiator {
|
||||
// Generate the set of features the negitator will present to the tower
|
||||
// upon connection. For anchor channels, we'll conditionally signal that
|
||||
// we require support for anchor channels depdening on the requested
|
||||
// policy.
|
||||
features := []lnwire.FeatureBit{
|
||||
wtwire.AltruistSessionsRequired,
|
||||
}
|
||||
if cfg.Policy.IsAnchorChannel() {
|
||||
features = append(features, wtwire.AnchorCommitRequired)
|
||||
}
|
||||
|
||||
localInit := wtwire.NewInitMessage(
|
||||
lnwire.NewRawFeatureVector(wtwire.AltruistSessionsRequired),
|
||||
lnwire.NewRawFeatureVector(features...),
|
||||
cfg.ChainHash,
|
||||
)
|
||||
|
||||
|
@@ -120,6 +120,11 @@ func (p Policy) String() string {
|
||||
p.SweepFeeRate)
|
||||
}
|
||||
|
||||
// IsAnchorChannel returns true if the session policy requires anchor channels.
|
||||
func (p Policy) IsAnchorChannel() bool {
|
||||
return p.TxPolicy.BlobType.IsAnchorChannel()
|
||||
}
|
||||
|
||||
// Validate ensures that the policy satisfies some minimal correctness
|
||||
// constraints.
|
||||
func (p Policy) Validate() error {
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var validationTests = []struct {
|
||||
@@ -91,3 +92,21 @@ func TestPolicyValidate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPolicyIsAnchorChannel asserts that the IsAnchorChannel helper properly
|
||||
// reflects the anchor bit of the policy's blob type.
|
||||
func TestPolicyIsAnchorChannel(t *testing.T) {
|
||||
policyNoAnchor := wtpolicy.Policy{
|
||||
TxPolicy: wtpolicy.TxPolicy{
|
||||
BlobType: blob.TypeAltruistCommit,
|
||||
},
|
||||
}
|
||||
require.Equal(t, false, policyNoAnchor.IsAnchorChannel())
|
||||
|
||||
policyAnchor := wtpolicy.Policy{
|
||||
TxPolicy: wtpolicy.TxPolicy{
|
||||
BlobType: blob.TypeAltruistAnchorCommit,
|
||||
},
|
||||
}
|
||||
require.Equal(t, true, policyAnchor.IsAnchorChannel())
|
||||
}
|
||||
|
@@ -96,7 +96,10 @@ type Server struct {
|
||||
// sessions and send state updates.
|
||||
func New(cfg *Config) (*Server, error) {
|
||||
localInit := wtwire.NewInitMessage(
|
||||
lnwire.NewRawFeatureVector(wtwire.AltruistSessionsOptional),
|
||||
lnwire.NewRawFeatureVector(
|
||||
wtwire.AltruistSessionsOptional,
|
||||
wtwire.AnchorCommitOptional,
|
||||
),
|
||||
cfg.ChainHash,
|
||||
)
|
||||
|
||||
|
@@ -161,6 +161,28 @@ type createSessionTestCase struct {
|
||||
}
|
||||
|
||||
var createSessionTests = []createSessionTestCase{
|
||||
{
|
||||
name: "duplicate session create altruist anchor commit",
|
||||
initMsg: wtwire.NewInitMessage(
|
||||
lnwire.NewRawFeatureVector(),
|
||||
testnetChainHash,
|
||||
),
|
||||
createMsg: &wtwire.CreateSession{
|
||||
BlobType: blob.TypeAltruistAnchorCommit,
|
||||
MaxUpdates: 1000,
|
||||
RewardBase: 0,
|
||||
RewardRate: 0,
|
||||
SweepFeeRate: 10000,
|
||||
},
|
||||
expReply: &wtwire.CreateSessionReply{
|
||||
Code: wtwire.CodeOK,
|
||||
Data: []byte{},
|
||||
},
|
||||
expDupReply: &wtwire.CreateSessionReply{
|
||||
Code: wtwire.CodeOK,
|
||||
Data: []byte{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate session create",
|
||||
initMsg: wtwire.NewInitMessage(
|
||||
|
@@ -7,6 +7,8 @@ import "github.com/lightningnetwork/lnd/lnwire"
|
||||
var FeatureNames = map[lnwire.FeatureBit]string{
|
||||
AltruistSessionsRequired: "altruist-sessions",
|
||||
AltruistSessionsOptional: "altruist-sessions",
|
||||
AnchorCommitRequired: "anchor-commit",
|
||||
AnchorCommitOptional: "anchor-commit",
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -19,4 +21,13 @@ const (
|
||||
// support a remote party who understand the protocol for creating and
|
||||
// updating watchtower sessions.
|
||||
AltruistSessionsOptional lnwire.FeatureBit = 1
|
||||
|
||||
// AnchorCommitRequired specifies that the advertising tower requires
|
||||
// the remote party to negotiate sessions for protecting anchor
|
||||
// channels.
|
||||
AnchorCommitRequired lnwire.FeatureBit = 2
|
||||
|
||||
// AnchorCommitOptional specifies that the advertising tower allows the
|
||||
// remote party to negotiate sessions for protecting anchor channels.
|
||||
AnchorCommitOptional lnwire.FeatureBit = 3
|
||||
)
|
||||
|
Reference in New Issue
Block a user