mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-20 10:39:01 +01:00
Merge pull request #3365 from Roasbeef/safu-commitments
multi: implement new safu commitment format
This commit is contained in:
commit
b1b4aab779
@ -944,17 +944,24 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
||||
|
||||
// First, record the breach information for the local channel point if
|
||||
// it is not considered dust, which is signaled by a non-nil sign
|
||||
// descriptor. Here we use CommitmentNoDelay since this output belongs
|
||||
// to us and has no time-based constraints on spending.
|
||||
// descriptor. Here we use CommitmentNoDelay (or
|
||||
// CommitmentNoDelayTweakless for newer commitments) since this output
|
||||
// belongs to us and has no time-based constraints on spending.
|
||||
if breachInfo.LocalOutputSignDesc != nil {
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if breachInfo.LocalOutputSignDesc.SingleTweak == nil {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
}
|
||||
|
||||
localOutput := makeBreachedOutput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
// No second level script as this is a commitment
|
||||
// output.
|
||||
nil,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
breachInfo.BreachHeight)
|
||||
breachInfo.BreachHeight,
|
||||
)
|
||||
|
||||
breachedOutputs = append(breachedOutputs, localOutput)
|
||||
}
|
||||
@ -972,7 +979,8 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
|
||||
// output.
|
||||
nil,
|
||||
breachInfo.RemoteOutputSignDesc,
|
||||
breachInfo.BreachHeight)
|
||||
breachInfo.BreachHeight,
|
||||
)
|
||||
|
||||
breachedOutputs = append(breachedOutputs, remoteOutput)
|
||||
}
|
||||
@ -1048,6 +1056,8 @@ func (b *breachArbiter) createJusticeTx(
|
||||
// type is unrecognized, we will omit it from the transaction.
|
||||
var witnessWeight int
|
||||
switch inp.WitnessType() {
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
witnessWeight = input.P2WKHWitnessSize
|
||||
|
||||
|
@ -90,6 +90,11 @@ var (
|
||||
0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21,
|
||||
0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d,
|
||||
},
|
||||
{0x02, 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b,
|
||||
0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21,
|
||||
0xa5, 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1,
|
||||
0xa3, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d,
|
||||
},
|
||||
}
|
||||
|
||||
breachedOutputs = []breachedOutput{
|
||||
@ -137,6 +142,42 @@ var (
|
||||
},
|
||||
secondLevelWitnessScript: breachKeys[0],
|
||||
},
|
||||
{
|
||||
amt: btcutil.Amount(1e7),
|
||||
outpoint: breachOutPoints[0],
|
||||
witnessType: input.CommitSpendNoDelayTweakless,
|
||||
signDesc: input.SignDescriptor{
|
||||
WitnessScript: []byte{
|
||||
0x00, 0x14, 0xee, 0x91, 0x41, 0x7e,
|
||||
0x85, 0x6c, 0xde, 0x10, 0xa2, 0x91,
|
||||
0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2,
|
||||
0xef, 0xb5, 0x71, 0x48,
|
||||
},
|
||||
Output: &wire.TxOut{
|
||||
Value: 5000000000,
|
||||
PkScript: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0,
|
||||
0x9e, 0xb1, 0xc5, 0xfe, 0x29,
|
||||
0x5a, 0xbd, 0xeb, 0x1d, 0xca,
|
||||
0x42, 0x81, 0xbe, 0x98, 0x8e,
|
||||
0x2d, 0xa0, 0xb6, 0xc1, 0xc6,
|
||||
0xa5, 0x9d, 0xc2, 0x26, 0xc2,
|
||||
0x86, 0x24, 0xe1, 0x81, 0x75,
|
||||
0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3,
|
||||
0x1f, 0x04, 0x78, 0x34, 0xbc,
|
||||
0x06, 0xd6, 0xd6, 0xed, 0xf6,
|
||||
0x20, 0xd1, 0x84, 0x24, 0x1a,
|
||||
0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
secondLevelWitnessScript: breachKeys[0],
|
||||
},
|
||||
{
|
||||
amt: btcutil.Amount(2e9),
|
||||
outpoint: breachOutPoints[1],
|
||||
@ -1339,7 +1380,8 @@ func testBreachSpends(t *testing.T, test breachTest) {
|
||||
|
||||
// Notify the breach arbiter about the breach.
|
||||
retribution, err := lnwallet.NewBreachRetribution(
|
||||
alice.State(), height, 1)
|
||||
alice.State(), height, 1,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create breach retribution: %v", err)
|
||||
}
|
||||
@ -1754,9 +1796,10 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
||||
}
|
||||
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
||||
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(channelBal,
|
||||
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
|
||||
*fundingTxIn)
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
|
||||
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
||||
bobCommitPoint, *fundingTxIn, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -1822,7 +1865,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
||||
IdentityPub: aliceKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: true,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: bobCommitPoint,
|
||||
@ -1840,7 +1883,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
|
||||
IdentityPub: bobKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: false,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: aliceCommitPoint,
|
||||
|
@ -21,11 +21,16 @@ import (
|
||||
type SingleBackupVersion byte
|
||||
|
||||
const (
|
||||
// DefaultSingleVersion is the defautl version of the single channel
|
||||
// backup. The seralized version of this static channel backup is
|
||||
// DefaultSingleVersion is the default version of the single channel
|
||||
// backup. The serialized version of this static channel backup is
|
||||
// simply: version || SCB. Where SCB is the known format of the
|
||||
// version.
|
||||
DefaultSingleVersion = 0
|
||||
|
||||
// TweaklessCommitVersion is the second SCB version. This version
|
||||
// implicitly denotes that this channel uses the new tweakless commit
|
||||
// format.
|
||||
TweaklessCommitVersion = 1
|
||||
)
|
||||
|
||||
// Single is a static description of an existing channel that can be used for
|
||||
@ -121,8 +126,7 @@ func NewSingle(channel *channeldb.OpenChannel,
|
||||
// key.
|
||||
_, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes())
|
||||
|
||||
return Single{
|
||||
Version: DefaultSingleVersion,
|
||||
single := Single{
|
||||
IsInitiator: channel.IsInitiator,
|
||||
ChainHash: channel.ChainHash,
|
||||
FundingOutpoint: channel.FundingOutpoint,
|
||||
@ -139,6 +143,14 @@ func NewSingle(channel *channeldb.OpenChannel,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if channel.ChanType.IsTweakless() {
|
||||
single.Version = TweaklessCommitVersion
|
||||
} else {
|
||||
single.Version = DefaultSingleVersion
|
||||
}
|
||||
|
||||
return single
|
||||
}
|
||||
|
||||
// Serialize attempts to write out the serialized version of the target
|
||||
@ -148,6 +160,7 @@ func (s *Single) Serialize(w io.Writer) error {
|
||||
// we're aware of.
|
||||
switch s.Version {
|
||||
case DefaultSingleVersion:
|
||||
case TweaklessCommitVersion:
|
||||
default:
|
||||
return fmt.Errorf("unable to serialize w/ unknown "+
|
||||
"version: %v", s.Version)
|
||||
@ -305,6 +318,7 @@ func (s *Single) Deserialize(r io.Reader) error {
|
||||
|
||||
switch s.Version {
|
||||
case DefaultSingleVersion:
|
||||
case TweaklessCommitVersion:
|
||||
default:
|
||||
return fmt.Errorf("unable to de-serialize w/ unknown "+
|
||||
"version: %v", s.Version)
|
||||
|
@ -124,8 +124,14 @@ func genRandomOpenChannelShell() (*channeldb.OpenChannel, error) {
|
||||
isInitiator = true
|
||||
}
|
||||
|
||||
chanType := channeldb.SingleFunder
|
||||
if rand.Int63()%2 == 0 {
|
||||
chanType = channeldb.SingleFunderTweakless
|
||||
}
|
||||
|
||||
return &channeldb.OpenChannel{
|
||||
ChainHash: chainHash,
|
||||
ChanType: chanType,
|
||||
IsInitiator: isInitiator,
|
||||
FundingOutpoint: chanPoint,
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(
|
||||
@ -223,6 +229,12 @@ func TestSinglePackUnpack(t *testing.T) {
|
||||
valid: true,
|
||||
},
|
||||
|
||||
// The new tweakless version, should pack/unpack with no problem.
|
||||
{
|
||||
version: TweaklessCommitVersion,
|
||||
valid: true,
|
||||
},
|
||||
|
||||
// A non-default version, atm this should result in a failure.
|
||||
{
|
||||
version: 99,
|
||||
@ -274,7 +286,7 @@ func TestSinglePackUnpack(t *testing.T) {
|
||||
}
|
||||
|
||||
rawBytes := rawSingle.Bytes()
|
||||
rawBytes[0] ^= 1
|
||||
rawBytes[0] ^= 5
|
||||
|
||||
newReader := bytes.NewReader(rawBytes)
|
||||
err = unpackedSingle.Deserialize(newReader)
|
||||
|
@ -135,14 +135,31 @@ const (
|
||||
|
||||
// SingleFunder represents a channel wherein one party solely funds the
|
||||
// entire capacity of the channel.
|
||||
SingleFunder = 0
|
||||
SingleFunder ChannelType = 0
|
||||
|
||||
// DualFunder represents a channel wherein both parties contribute
|
||||
// funds towards the total capacity of the channel. The channel may be
|
||||
// funded symmetrically or asymmetrically.
|
||||
DualFunder = 1
|
||||
DualFunder ChannelType = 1
|
||||
|
||||
// SingleFunderTweakless is similar to the basic SingleFunder channel
|
||||
// type, but it omits the tweak for one's key in the commitment
|
||||
// transaction of the remote party.
|
||||
SingleFunderTweakless ChannelType = 2
|
||||
)
|
||||
|
||||
// IsSingleFunder returns true if the channel type if one of the known single
|
||||
// funder variants.
|
||||
func (c ChannelType) IsSingleFunder() bool {
|
||||
return c == SingleFunder || c == SingleFunderTweakless
|
||||
}
|
||||
|
||||
// IsTweakless returns true if the target channel uses a commitment that
|
||||
// doesn't tweak the key for the remote party.
|
||||
func (c ChannelType) IsTweakless() bool {
|
||||
return c == SingleFunderTweakless
|
||||
}
|
||||
|
||||
// ChannelConstraints represents a set of constraints meant to allow a node to
|
||||
// limit their exposure, enact flow control and ensure that all HTLCs are
|
||||
// economically relevant. This struct will be mirrored for both sides of the
|
||||
@ -852,6 +869,14 @@ func (c *OpenChannel) ChanSyncMsg() (*lnwire.ChannelReestablish, error) {
|
||||
// allowing us to sweep our funds.
|
||||
if c.hasChanStatus(ChanStatusRestored) {
|
||||
currentCommitSecret[0] ^= 1
|
||||
|
||||
// If this is a tweakless channel, then we'll purposefully send
|
||||
// a next local height taht's invalid to trigger a force close
|
||||
// on their end. We do this as tweakless channels don't require
|
||||
// that the commitment point is valid, only that it's present.
|
||||
if c.ChanType.IsTweakless() {
|
||||
nextLocalCommitHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
return &lnwire.ChannelReestablish{
|
||||
@ -2506,7 +2531,7 @@ func putChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated, write the funding txn.
|
||||
if channel.ChanType == SingleFunder && channel.IsInitiator &&
|
||||
if channel.ChanType.IsSingleFunder() && channel.IsInitiator &&
|
||||
!channel.hasChanStatus(ChanStatusRestored) {
|
||||
|
||||
if err := WriteElement(&w, channel.FundingTxn); err != nil {
|
||||
@ -2628,7 +2653,7 @@ func fetchChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
|
||||
}
|
||||
|
||||
// For single funder channels that we initiated, read the funding txn.
|
||||
if channel.ChanType == SingleFunder && channel.IsInitiator &&
|
||||
if channel.ChanType.IsSingleFunder() && channel.IsInitiator &&
|
||||
!channel.hasChanStatus(ChanStatusRestored) {
|
||||
|
||||
if err := ReadElement(r, &channel.FundingTxn); err != nil {
|
||||
|
@ -83,9 +83,23 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
|
||||
return nil, fmt.Errorf("unable to derive htlc key: %v", err)
|
||||
}
|
||||
|
||||
var chanType channeldb.ChannelType
|
||||
switch backup.Version {
|
||||
|
||||
case chanbackup.DefaultSingleVersion:
|
||||
chanType = channeldb.SingleFunder
|
||||
|
||||
case chanbackup.TweaklessCommitVersion:
|
||||
chanType = channeldb.SingleFunderTweakless
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown Single version: %v", err)
|
||||
}
|
||||
|
||||
chanShell := channeldb.ChannelShell{
|
||||
NodeAddrs: backup.Addresses,
|
||||
Chan: &channeldb.OpenChannel{
|
||||
ChanType: chanType,
|
||||
ChainHash: backup.ChainHash,
|
||||
IsInitiator: backup.IsInitiator,
|
||||
Capacity: backup.Capacity,
|
||||
|
@ -34,7 +34,7 @@ func TestChainArbitratorRepublishCommitment(t *testing.T) {
|
||||
const numChans = 10
|
||||
var channels []*channeldb.OpenChannel
|
||||
for i := 0; i < numChans; i++ {
|
||||
lChannel, _, cleanup, err := lnwallet.CreateTestChannels()
|
||||
lChannel, _, cleanup, err := lnwallet.CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
|
||||
// based off of only the set of outputs included.
|
||||
func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
|
||||
commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
|
||||
revocationProducer shachain.Producer) (bool, error) {
|
||||
revocationProducer shachain.Producer, tweakless bool) (bool, error) {
|
||||
|
||||
// First, we'll re-derive our commitment point for this state since
|
||||
// this is what we use to randomize each of the keys for this state.
|
||||
@ -344,14 +344,15 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
|
||||
// Now that we have the commit point, we'll derive the tweaked local
|
||||
// and remote keys for this state. We use our point as only we can
|
||||
// revoke our own commitment.
|
||||
localDelayBasePoint := localChanCfg.DelayBasePoint.PubKey
|
||||
localDelayKey := input.TweakPubKey(localDelayBasePoint, commitPoint)
|
||||
remoteNonDelayPoint := remoteChanCfg.PaymentBasePoint.PubKey
|
||||
remotePayKey := input.TweakPubKey(remoteNonDelayPoint, commitPoint)
|
||||
commitKeyRing := lnwallet.DeriveCommitmentKeys(
|
||||
commitPoint, true, tweakless, &localChanCfg, &remoteChanCfg,
|
||||
)
|
||||
|
||||
// With the keys derived, we'll construct the remote script that'll be
|
||||
// present if they have a non-dust balance on the commitment.
|
||||
remotePkScript, err := input.CommitScriptUnencumbered(remotePayKey)
|
||||
remotePkScript, err := input.CommitScriptUnencumbered(
|
||||
commitKeyRing.NoDelayKey,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -359,11 +360,9 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
|
||||
// Next, we'll derive our script that includes the revocation base for
|
||||
// the remote party allowing them to claim this output before the CSV
|
||||
// delay if we breach.
|
||||
revocationKey := input.DeriveRevocationPubkey(
|
||||
remoteChanCfg.RevocationBasePoint.PubKey, commitPoint,
|
||||
)
|
||||
localScript, err := input.CommitScriptToSelf(
|
||||
uint32(localChanCfg.CsvDelay), localDelayKey, revocationKey,
|
||||
uint32(localChanCfg.CsvDelay), commitKeyRing.DelayKey,
|
||||
commitKeyRing.RevocationKey,
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -423,6 +422,11 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
|
||||
// revoked state...!!!
|
||||
commitTxBroadcast := commitSpend.SpendingTx
|
||||
|
||||
// An additional piece of information we need to properly
|
||||
// dispatch a close event if is this channel was using the
|
||||
// tweakless remove key format or not.
|
||||
tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
|
||||
|
||||
localCommit, remoteCommit, err := c.cfg.chanState.LatestCommitments()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to fetch channel state for "+
|
||||
@ -480,6 +484,7 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
|
||||
c.cfg.chanState.LocalChanCfg,
|
||||
c.cfg.chanState.RemoteChanCfg, commitSpend,
|
||||
broadcastStateNum, c.cfg.chanState.RevocationProducer,
|
||||
tweaklessCommit,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("unable to determine self commit for "+
|
||||
@ -584,48 +589,29 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
|
||||
"state #%v!!! Attempting recovery...",
|
||||
broadcastStateNum, remoteStateNum)
|
||||
|
||||
// If we are lucky, the remote peer sent us the correct
|
||||
// commitment point during channel sync, such that we
|
||||
// can sweep our funds. If we cannot find the commit
|
||||
// point, there's not much we can do other than wait
|
||||
// for us to retrieve it. We will attempt to retrieve
|
||||
// it from the peer each time we connect to it.
|
||||
//
|
||||
// TODO(halseth): actively initiate re-connection to
|
||||
// the peer?
|
||||
var commitPoint *btcec.PublicKey
|
||||
backoff := minCommitPointPollTimeout
|
||||
for {
|
||||
commitPoint, err = c.cfg.chanState.DataLossCommitPoint()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Errorf("Unable to retrieve commitment "+
|
||||
"point for channel(%v) with lost "+
|
||||
"state: %v. Retrying in %v.",
|
||||
c.cfg.chanState.FundingOutpoint,
|
||||
err, backoff)
|
||||
|
||||
select {
|
||||
// Wait before retrying, with an exponential
|
||||
// backoff.
|
||||
case <-time.After(backoff):
|
||||
backoff = 2 * backoff
|
||||
if backoff > maxCommitPointPollTimeout {
|
||||
backoff = maxCommitPointPollTimeout
|
||||
}
|
||||
|
||||
case <-c.quit:
|
||||
// If this isn't a tweakless commitment, then we'll
|
||||
// need to wait for the remote party's latest unrevoked
|
||||
// commitment point to be presented to us as we need
|
||||
// this to sweep. Otherwise, we can dispatch the remote
|
||||
// close and sweep immediately using a fake commitPoint
|
||||
// as it isn't actually needed for recovery anymore.
|
||||
commitPoint := c.cfg.chanState.RemoteCurrentRevocation
|
||||
if !tweaklessCommit {
|
||||
commitPoint = c.waitForCommitmentPoint()
|
||||
if commitPoint == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Recovered commit point(%x) for "+
|
||||
"channel(%v)! Now attempting to use it to "+
|
||||
"sweep our funds...",
|
||||
commitPoint.SerializeCompressed(),
|
||||
c.cfg.chanState.FundingOutpoint)
|
||||
log.Infof("Recovered commit point(%x) for "+
|
||||
"channel(%v)! Now attempting to use it to "+
|
||||
"sweep our funds...",
|
||||
commitPoint.SerializeCompressed(),
|
||||
c.cfg.chanState.FundingOutpoint)
|
||||
|
||||
} else {
|
||||
log.Infof("ChannelPoint(%v) is tweakless, " +
|
||||
"moving to sweep directly on chain")
|
||||
}
|
||||
|
||||
// Since we don't have the commitment stored for this
|
||||
// state, we'll just pass an empty commitment within
|
||||
@ -1009,3 +995,39 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForCommitmentPoint waits for the commitment point to be inserted into
|
||||
// the local database. We'll use this method in the DLP case, to wait for the
|
||||
// remote party to send us their point, as we can't proceed until we have that.
|
||||
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
|
||||
// If we are lucky, the remote peer sent us the correct commitment
|
||||
// point during channel sync, such that we can sweep our funds. If we
|
||||
// cannot find the commit point, there's not much we can do other than
|
||||
// wait for us to retrieve it. We will attempt to retrieve it from the
|
||||
// peer each time we connect to it.
|
||||
//
|
||||
// TODO(halseth): actively initiate re-connection to the peer?
|
||||
backoff := minCommitPointPollTimeout
|
||||
for {
|
||||
commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
|
||||
if err == nil {
|
||||
return commitPoint
|
||||
}
|
||||
|
||||
log.Errorf("Unable to retrieve commitment point for "+
|
||||
"channel(%v) with lost state: %v. Retrying in %v.",
|
||||
c.cfg.chanState.FundingOutpoint, err, backoff)
|
||||
|
||||
select {
|
||||
// Wait before retrying, with an exponential backoff.
|
||||
case <-time.After(backoff):
|
||||
backoff = 2 * backoff
|
||||
if backoff > maxCommitPointPollTimeout {
|
||||
backoff = maxCommitPointPollTimeout
|
||||
}
|
||||
|
||||
case <-c.quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func TestChainWatcherRemoteUnilateralClose(t *testing.T) {
|
||||
|
||||
// First, we'll create two channels which already have established a
|
||||
// commitment contract between themselves.
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -149,7 +149,7 @@ func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) {
|
||||
|
||||
// First, we'll create two channels which already have established a
|
||||
// commitment contract between themselves.
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -272,7 +272,9 @@ func TestChainWatcherDataLossProtect(t *testing.T) {
|
||||
dlpScenario := func(t *testing.T, testCase dlpTestCase) bool {
|
||||
// First, we'll create two channels which already have
|
||||
// established a commitment contract between themselves.
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels(
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -430,7 +432,7 @@ func TestChainWatcherDataLossProtect(t *testing.T) {
|
||||
func TestChainWatcherLocalForceCloseDetect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// localForceCloseScenario is the primary test we'll use to execut eout
|
||||
// localForceCloseScenario is the primary test we'll use to execute our
|
||||
// table driven tests. We'll assert that for any number of state
|
||||
// updates, and if the commitment transaction has our output or not,
|
||||
// we're able to properly detect a local force close.
|
||||
@ -439,7 +441,9 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) {
|
||||
|
||||
// First, we'll create two channels which already have
|
||||
// established a commitment contract between themselves.
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels(
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
|
@ -89,12 +89,23 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
|
||||
isLocalCommitTx := c.commitResolution.MaturityDelay != 0
|
||||
|
||||
if !isLocalCommitTx {
|
||||
// There're two types of commitments, those that have tweaks
|
||||
// for the remote key (us in this case), and those that don't.
|
||||
// We'll rely on the presence of the commitment tweak to to
|
||||
// discern which type of commitment this is.
|
||||
var witnessType input.WitnessType
|
||||
if c.commitResolution.SelfOutputSignDesc.SingleTweak == nil {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
} else {
|
||||
witnessType = input.CommitmentNoDelay
|
||||
}
|
||||
|
||||
// We'll craft an input with all the information required for
|
||||
// the sweeper to create a fully valid sweeping transaction to
|
||||
// recover these coins.
|
||||
inp := input.MakeBaseInput(
|
||||
&c.commitResolution.SelfOutPoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
&c.commitResolution.SelfOutputSignDesc,
|
||||
c.broadcastHeight,
|
||||
)
|
||||
|
@ -56,6 +56,12 @@ func (p *mockPeer) Address() net.Addr { return nil }
|
||||
func (p *mockPeer) QuitSignal() <-chan struct{} {
|
||||
return p.quit
|
||||
}
|
||||
func (p *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
func (p *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mockMessageStore is an in-memory implementation of the MessageStore interface
|
||||
// used for the gossiper's unit tests.
|
||||
|
@ -1199,6 +1199,18 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
// reservation attempt may be rejected. Note that since we're on the
|
||||
// responding side of a single funder workflow, we don't commit any
|
||||
// funds to the channel ourselves.
|
||||
//
|
||||
// Before we init the channel, we'll also check to see if we've
|
||||
// negotiated the new tweakless commitment format. This is only the
|
||||
// case if *both* us and the remote peer are signaling the proper
|
||||
// feature bit.
|
||||
localTweakless := fmsg.peer.LocalGlobalFeatures().HasFeature(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
)
|
||||
remoteTweakless := fmsg.peer.RemoteGlobalFeatures().HasFeature(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
)
|
||||
tweaklessCommitment := localTweakless && remoteTweakless
|
||||
chainHash := chainhash.Hash(msg.ChainHash)
|
||||
req := &lnwallet.InitFundingReserveMsg{
|
||||
ChainHash: &chainHash,
|
||||
@ -1211,6 +1223,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
PushMSat: msg.PushAmount,
|
||||
Flags: msg.ChannelFlags,
|
||||
MinConfs: 1,
|
||||
Tweakless: tweaklessCommitment,
|
||||
}
|
||||
|
||||
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
|
||||
@ -1246,8 +1259,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
}
|
||||
|
||||
fndgLog.Infof("Requiring %v confirmations for pendingChan(%x): "+
|
||||
"amt=%v, push_amt=%v", numConfsReq, fmsg.msg.PendingChannelID,
|
||||
amt, msg.PushAmount)
|
||||
"amt=%v, push_amt=%v, tweakless=%v", numConfsReq,
|
||||
fmsg.msg.PendingChannelID, amt, msg.PushAmount,
|
||||
tweaklessCommitment)
|
||||
|
||||
// Generate our required constraints for the remote party.
|
||||
remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt)
|
||||
@ -2440,7 +2454,7 @@ func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) {
|
||||
// Launch a defer so we _ensure_ that the channel barrier is properly
|
||||
// closed even if the target peer is no longer online at this point.
|
||||
defer func() {
|
||||
// Close the active channel barrier signalling the readHandler
|
||||
// Close the active channel barrier signaling the readHandler
|
||||
// that commitment related modifications to this channel can
|
||||
// now proceed.
|
||||
f.barrierMtx.Lock()
|
||||
@ -2748,6 +2762,18 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
// Initialize a funding reservation with the local wallet. If the
|
||||
// wallet doesn't have enough funds to commit to this channel, then the
|
||||
// request will fail, and be aborted.
|
||||
//
|
||||
// Before we init the channel, we'll also check to see if we've
|
||||
// negotiated the new tweakless commitment format. This is only the
|
||||
// case if *both* us and the remote peer are signaling the proper
|
||||
// feature bit.
|
||||
localTweakless := msg.peer.LocalGlobalFeatures().HasFeature(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
)
|
||||
remoteTweakless := msg.peer.RemoteGlobalFeatures().HasFeature(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
)
|
||||
tweaklessCommitment := localTweakless && remoteTweakless
|
||||
req := &lnwallet.InitFundingReserveMsg{
|
||||
ChainHash: &msg.chainHash,
|
||||
NodeID: peerKey,
|
||||
@ -2760,6 +2786,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
PushMSat: msg.pushAmt,
|
||||
Flags: channelFlags,
|
||||
MinConfs: msg.minConfs,
|
||||
Tweakless: tweaklessCommitment,
|
||||
}
|
||||
|
||||
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
|
||||
@ -2828,8 +2855,8 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
|
||||
maxValue := f.cfg.RequiredRemoteMaxValue(capacity)
|
||||
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(capacity)
|
||||
|
||||
fndgLog.Infof("Starting funding workflow with %v for pendingID(%x)",
|
||||
msg.peer.Address(), chanID)
|
||||
fndgLog.Infof("Starting funding workflow with %v for pendingID(%x), "+
|
||||
"tweakless=%v", msg.peer.Address(), chanID, tweaklessCommitment)
|
||||
|
||||
fundingOpen := lnwire.OpenChannel{
|
||||
ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash,
|
||||
|
@ -183,6 +183,14 @@ func (n *testNode) QuitSignal() <-chan struct{} {
|
||||
return n.shutdownChannel
|
||||
}
|
||||
|
||||
func (n *testNode) LocalGlobalFeatures() *lnwire.FeatureVector {
|
||||
return lnwire.NewFeatureVector(nil, nil)
|
||||
}
|
||||
|
||||
func (n *testNode) RemoteGlobalFeatures() *lnwire.FeatureVector {
|
||||
return lnwire.NewFeatureVector(nil, nil)
|
||||
}
|
||||
|
||||
func (n *testNode) AddNewChannel(channel *channeldb.OpenChannel,
|
||||
quit <-chan struct{}) error {
|
||||
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lightning-onion"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
)
|
||||
|
||||
|
@ -175,6 +175,8 @@ type TowerClient interface {
|
||||
// state. If the method returns nil, the backup is guaranteed to be
|
||||
// successful unless the tower is unavailable and client is force quit,
|
||||
// or the justice transaction would create dust outputs when trying to
|
||||
// abide by the negotiated policy.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution) error
|
||||
// abide by the 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
|
||||
}
|
||||
|
@ -954,7 +954,7 @@ func (l *channelLink) htlcManager() {
|
||||
|
||||
l.fail(
|
||||
LinkFailureError{
|
||||
code: ErrSyncError,
|
||||
code: ErrRecoveryError,
|
||||
ForceClose: false,
|
||||
},
|
||||
"unable to synchronize channel "+
|
||||
@ -1844,8 +1844,13 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
chanType := l.channel.State().ChanType
|
||||
isTweakless := chanType == channeldb.SingleFunderTweakless
|
||||
|
||||
chanID := l.ChanID()
|
||||
err = l.cfg.TowerClient.BackupState(&chanID, breachInfo)
|
||||
err = l.cfg.TowerClient.BackupState(
|
||||
&chanID, breachInfo, isTweakless,
|
||||
)
|
||||
if err != nil {
|
||||
l.fail(LinkFailureError{code: ErrInternalError},
|
||||
"unable to queue breach backup: %v", err)
|
||||
|
@ -1651,6 +1651,12 @@ func (m *mockPeer) IdentityKey() *btcec.PublicKey {
|
||||
func (m *mockPeer) Address() net.Addr {
|
||||
return nil
|
||||
}
|
||||
func (m *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
func (m *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) (
|
||||
ChannelLink, *lnwallet.LightningChannel, chan time.Time, func() error,
|
||||
|
@ -34,6 +34,11 @@ const (
|
||||
// ErrInvalidRevocation indicates that the remote peer send us an
|
||||
// invalid revocation message.
|
||||
ErrInvalidRevocation
|
||||
|
||||
// ErrRecoveryError the channel was unable to be resumed, we need the
|
||||
// remote party to force close the channel out on chain now as a
|
||||
// result.
|
||||
ErrRecoveryError
|
||||
)
|
||||
|
||||
// LinkFailureError encapsulates an error that will make us fail the current
|
||||
@ -74,6 +79,8 @@ func (e LinkFailureError) Error() string {
|
||||
return "invalid commitment"
|
||||
case ErrInvalidRevocation:
|
||||
return "invalid revocation"
|
||||
case ErrRecoveryError:
|
||||
return "unable to resume channel, recovery required"
|
||||
default:
|
||||
return "unknown error"
|
||||
}
|
||||
|
@ -597,6 +597,14 @@ func (s *mockServer) WipeChannel(*wire.OutPoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mockServer) LocalGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mockServer) RemoteGlobalFeatures() *lnwire.FeatureVector {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mockServer) Stop() error {
|
||||
if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) {
|
||||
return nil
|
||||
|
@ -257,9 +257,10 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
}
|
||||
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
||||
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(aliceAmount,
|
||||
bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
|
||||
*fundingTxIn)
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
|
||||
aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint,
|
||||
bobCommitPoint, *fundingTxIn, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -326,7 +327,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
RemoteChanCfg: bobCfg,
|
||||
IdentityPub: aliceKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: true,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: bobCommitPoint,
|
||||
@ -345,7 +346,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||
RemoteChanCfg: aliceCfg,
|
||||
IdentityPub: bobKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: false,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: aliceCommitPoint,
|
||||
|
@ -902,13 +902,15 @@ func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
|
||||
}
|
||||
|
||||
// CommitSpendNoDelay constructs a valid witness allowing a node to spend their
|
||||
// settled no-delay output on the counterparty's commitment transaction.
|
||||
// settled no-delay output on the counterparty's commitment transaction. If the
|
||||
// tweakless field is true, then we'll omit the set where we tweak the pubkey
|
||||
// with a random set of bytes, and use it directly in the witness stack.
|
||||
//
|
||||
// NOTE: The passed SignDescriptor should include the raw (untweaked) public
|
||||
// key of the receiver and also the proper single tweak value based on the
|
||||
// current commitment point.
|
||||
func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
|
||||
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
|
||||
sweepTx *wire.MsgTx, tweakless bool) (wire.TxWitness, error) {
|
||||
|
||||
if signDesc.KeyDesc.PubKey == nil {
|
||||
return nil, fmt.Errorf("cannot generate witness with nil " +
|
||||
@ -923,14 +925,25 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
|
||||
}
|
||||
|
||||
// Finally, we'll manually craft the witness. The witness here is the
|
||||
// exact same as a regular p2wkh witness, but we'll need to ensure that
|
||||
// we use the tweaked public key as the last item in the witness stack
|
||||
// which was originally used to created the pkScript we're spending.
|
||||
// exact same as a regular p2wkh witness, depending on the value of the
|
||||
// tweakless bool.
|
||||
witness := make([][]byte, 2)
|
||||
witness[0] = append(sweepSig, byte(signDesc.HashType))
|
||||
witness[1] = TweakPubKeyWithTweak(
|
||||
signDesc.KeyDesc.PubKey, signDesc.SingleTweak,
|
||||
).SerializeCompressed()
|
||||
|
||||
switch tweakless {
|
||||
// If we're tweaking the key, then we use the tweaked public key as the
|
||||
// last item in the witness stack which was originally used to created
|
||||
// the pkScript we're spending.
|
||||
case false:
|
||||
witness[1] = TweakPubKeyWithTweak(
|
||||
signDesc.KeyDesc.PubKey, signDesc.SingleTweak,
|
||||
).SerializeCompressed()
|
||||
|
||||
// Otherwise, we can just use the raw pubkey, since there's no random
|
||||
// value to be combined.
|
||||
case true:
|
||||
witness[1] = signDesc.KeyDesc.PubKey.SerializeCompressed()
|
||||
}
|
||||
|
||||
return witness, nil
|
||||
}
|
||||
|
@ -79,6 +79,11 @@ const (
|
||||
// output that sends to a nested P2SH script that pays to a key solely
|
||||
// under our control. The witness generated needs to include the
|
||||
NestedWitnessKeyHash WitnessType = 11
|
||||
|
||||
// CommitSpendNoDelayTweakless is similar to the CommitSpendNoDelay
|
||||
// type, but it omits the tweak that randomizes the key we need to
|
||||
// spend with a channel peer supplied set of randomness.
|
||||
CommitSpendNoDelayTweakless = 12
|
||||
)
|
||||
|
||||
// Stirng returns a human readable version of the target WitnessType.
|
||||
@ -90,6 +95,9 @@ func (wt WitnessType) String() string {
|
||||
case CommitmentNoDelay:
|
||||
return "CommitmentNoDelay"
|
||||
|
||||
case CommitSpendNoDelayTweakless:
|
||||
return "CommitmentNoDelayTweakless"
|
||||
|
||||
case CommitmentRevoke:
|
||||
return "CommitmentRevoke"
|
||||
|
||||
@ -153,7 +161,17 @@ func (wt WitnessType) GenWitnessFunc(signer Signer,
|
||||
}, nil
|
||||
|
||||
case CommitmentNoDelay:
|
||||
witness, err := CommitSpendNoDelay(signer, desc, tx)
|
||||
witness, err := CommitSpendNoDelay(signer, desc, tx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Script{
|
||||
Witness: witness,
|
||||
}, nil
|
||||
|
||||
case CommitSpendNoDelayTweakless:
|
||||
witness, err := CommitSpendNoDelay(signer, desc, tx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,3 +14,9 @@ type LegacyProtocol struct {
|
||||
func (l *LegacyProtocol) LegacyOnion() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// LegacyOnion returns true if the old commitment format should be used for new
|
||||
// funded channels.
|
||||
func (l *LegacyProtocol) LegacyCommitment() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -10,6 +10,12 @@ type LegacyProtocol struct {
|
||||
// As a result, nodes that include us in the route won't use the new
|
||||
// modern onion framing.
|
||||
Onion bool `long:"onion" description:"force node to not advertise the new modern TLV onion format"`
|
||||
|
||||
// CommitmentTweak guards if we should use the old legacy commitment
|
||||
// protocol, or the newer variant that doesn't have a tweak for the
|
||||
// remote party's output in the commitment. If set to true, then we
|
||||
// won't signal StaticRemoteKeyOptional.
|
||||
CommitmentTweak bool `long:"committweak" description:"force node to not advertise the new commitment format"`
|
||||
}
|
||||
|
||||
// LegacyOnion returns true if the old legacy onion format should be used when
|
||||
@ -18,3 +24,9 @@ type LegacyProtocol struct {
|
||||
func (l *LegacyProtocol) LegacyOnion() bool {
|
||||
return l.Onion
|
||||
}
|
||||
|
||||
// LegacyOnion returns true if the old commitment format should be used for new
|
||||
// funded channels.
|
||||
func (l *LegacyProtocol) LegacyCommitment() bool {
|
||||
return l.CommitmentTweak
|
||||
}
|
||||
|
@ -46,4 +46,16 @@ type Peer interface {
|
||||
// using the interface to cancel any processing in the event the backing
|
||||
// implementation exits.
|
||||
QuitSignal() <-chan struct{}
|
||||
|
||||
// LocalGlobalFeatures returns the set of global features that has been
|
||||
// advertised by the local peer. This allows sub-systems that use this
|
||||
// interface to gate their behavior off the set of negotiated feature
|
||||
// bits.
|
||||
LocalGlobalFeatures() *lnwire.FeatureVector
|
||||
|
||||
// RemoteGlobalFeatures returns the set of global features that has
|
||||
// been advertised by the remote peer. This allows sub-systems that use
|
||||
// this interface to gate their behavior off the set of negotiated
|
||||
// feature bits.
|
||||
RemoteGlobalFeatures() *lnwire.FeatureVector
|
||||
}
|
||||
|
1082
lnrpc/rpc.pb.go
1082
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -1237,6 +1237,14 @@ message Channel {
|
||||
The minimum satoshis the other node is required to reserve in its balance.
|
||||
*/
|
||||
int64 remote_chan_reserve_sat = 21 [json_name = "remote_chan_reserve_sat"];
|
||||
|
||||
/**
|
||||
If true, then this channel uses the modern commitment format where the key
|
||||
in the output of the remote party does not change each state. This makes
|
||||
back up and recovery easier as when the channel is closed, the funds go
|
||||
directly to that key.
|
||||
*/
|
||||
bool static_remote_key = 22 [json_name = "static_remote_key"];
|
||||
}
|
||||
|
||||
|
||||
|
@ -1654,6 +1654,11 @@
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "*\nThe minimum satoshis the other node is required to reserve in its balance."
|
||||
},
|
||||
"static_remote_key": {
|
||||
"type": "boolean",
|
||||
"format": "boolean",
|
||||
"description": "*\nIf true, then this channel uses the modern commitment format where the key\nin the output of the remote party does not change each state. This makes\nback up and recovery easier as when the channel is closed, the funds go\ndirectly to that key."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -927,13 +927,13 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 20, nil)
|
||||
}
|
||||
|
||||
// testBasicChannelFunding performs a test exercising expected behavior from a
|
||||
// basic funding workflow. The test creates a new channel between Alice and
|
||||
// Bob, then immediately closes the channel after asserting some expected post
|
||||
// conditions. Finally, the chain itself is checked to ensure the closing
|
||||
// transaction was mined.
|
||||
func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
ctxb := context.Background()
|
||||
// basicChannelFundingTest is a sub-test of the main testBasicChannelFunding
|
||||
// test. Given two nodes: Alice and Bob, it'll assert proper channel creation,
|
||||
// then return a function closure that should be called to assert proper
|
||||
// channel closure.
|
||||
func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness,
|
||||
alice *lntest.HarnessNode,
|
||||
bob *lntest.HarnessNode) (*lnrpc.Channel, *lnrpc.Channel, func(), error) {
|
||||
|
||||
chanAmt := lnd.MaxBtcFundingAmount
|
||||
pushAmt := btcutil.Amount(100000)
|
||||
@ -944,9 +944,10 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
// open or an error occurs in the funding process. A series of
|
||||
// assertions will be executed to ensure the funding process completed
|
||||
// successfully.
|
||||
ctxb := context.Background()
|
||||
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
|
||||
chanPoint := openChannelAndAssert(
|
||||
ctxt, t, net, net.Alice, net.Bob,
|
||||
ctxt, t, net, alice, bob,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
@ -954,42 +955,160 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
)
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("alice didn't report channel: %v", err)
|
||||
return nil, nil, nil, fmt.Errorf("alice didn't report "+
|
||||
"channel: %v", err)
|
||||
}
|
||||
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("bob didn't report channel: %v", err)
|
||||
return nil, nil, nil, fmt.Errorf("bob didn't report "+
|
||||
"channel: %v", err)
|
||||
}
|
||||
|
||||
// With the channel open, ensure that the amount specified above has
|
||||
// properly been pushed to Bob.
|
||||
balReq := &lnrpc.ChannelBalanceRequest{}
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
aliceBal, err := net.Alice.ChannelBalance(ctxt, balReq)
|
||||
aliceBal, err := alice.ChannelBalance(ctxt, balReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get alice's balance: %v", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to get alice's "+
|
||||
"balance: %v", err)
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
bobBal, err := net.Bob.ChannelBalance(ctxt, balReq)
|
||||
bobBal, err := bob.ChannelBalance(ctxt, balReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get bobs's balance: %v", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to get bobs's "+
|
||||
"balance: %v", err)
|
||||
}
|
||||
if aliceBal.Balance != int64(chanAmt-pushAmt-calcStaticFee(0)) {
|
||||
t.Fatalf("alice's balance is incorrect: expected %v got %v",
|
||||
return nil, nil, nil, fmt.Errorf("alice's balance is "+
|
||||
"incorrect: expected %v got %v",
|
||||
chanAmt-pushAmt-calcStaticFee(0), aliceBal)
|
||||
}
|
||||
if bobBal.Balance != int64(pushAmt) {
|
||||
t.Fatalf("bob's balance is incorrect: expected %v got %v",
|
||||
pushAmt, bobBal.Balance)
|
||||
return nil, nil, nil, fmt.Errorf("bob's balance is incorrect: "+
|
||||
"expected %v got %v", pushAmt, bobBal.Balance)
|
||||
}
|
||||
|
||||
// Finally, immediately close the channel. This function will also
|
||||
// block until the channel is closed and will additionally assert the
|
||||
// relevant channel closing post conditions.
|
||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
||||
req := &lnrpc.ListChannelsRequest{}
|
||||
aliceChannel, err := alice.ListChannels(context.Background(), req)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to obtain chan: %v", err)
|
||||
}
|
||||
|
||||
bobChannel, err := bob.ListChannels(context.Background(), req)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to obtain chan: %v", err)
|
||||
}
|
||||
|
||||
closeChan := func() {
|
||||
// Finally, immediately close the channel. This function will
|
||||
// also block until the channel is closed and will additionally
|
||||
// assert the relevant channel closing post conditions.
|
||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||
closeChannelAndAssert(ctxt, t, net, alice, chanPoint, false)
|
||||
}
|
||||
|
||||
return aliceChannel.Channels[0], bobChannel.Channels[0], closeChan, nil
|
||||
}
|
||||
|
||||
// testBasicChannelFunding performs a test exercising expected behavior from a
|
||||
// basic funding workflow. The test creates a new channel between Alice and
|
||||
// Bob, then immediately closes the channel after asserting some expected post
|
||||
// conditions. Finally, the chain itself is checked to ensure the closing
|
||||
// transaction was mined.
|
||||
func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
|
||||
ctxb := context.Background()
|
||||
|
||||
test:
|
||||
// We'll test all possible combinations of the feature bit presence
|
||||
// that both nodes can signal for this new channel type. We'll make a
|
||||
// new Carol+Dave for each test instance as well.
|
||||
for _, carolTweakless := range []bool{true, false} {
|
||||
for _, daveTweakless := range []bool{true, false} {
|
||||
// Based on the current tweak variable for Carol, we'll
|
||||
// preferentially signal the legacy commitment format.
|
||||
// We do the same for Dave shortly below.
|
||||
var carolArgs []string
|
||||
if !carolTweakless {
|
||||
carolArgs = []string{"--legacyprotocol.committweak"}
|
||||
}
|
||||
carol, err := net.NewNode("Carol", carolArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new node: %v", err)
|
||||
}
|
||||
|
||||
// Each time, we'll send Carol a new set of coins in
|
||||
// order to fund the channel.
|
||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send coins to carol: %v", err)
|
||||
}
|
||||
|
||||
var daveArgs []string
|
||||
if !daveTweakless {
|
||||
daveArgs = []string{"--legacyprotocol.committweak"}
|
||||
}
|
||||
dave, err := net.NewNode("Dave", daveArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create new node: %v", err)
|
||||
}
|
||||
|
||||
// Before we start the test, we'll ensure both sides
|
||||
// are connected to the funding flow can properly be
|
||||
// executed.
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
err = net.EnsureConnected(ctxt, carol, dave)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect peers: %v", err)
|
||||
}
|
||||
|
||||
testName := fmt.Sprintf("carol_tweak=%v,dave_tweak=%v",
|
||||
carolTweakless, daveTweakless)
|
||||
|
||||
ht := t
|
||||
success := t.t.Run(testName, func(t *testing.T) {
|
||||
carolChannel, daveChannel, closeChan, err := basicChannelFundingTest(
|
||||
ht, net, carol, dave,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed funding flow: %v", err)
|
||||
}
|
||||
|
||||
tweaklessSignalled := carolTweakless && daveTweakless
|
||||
tweaklessChans := (carolChannel.StaticRemoteKey &&
|
||||
daveChannel.StaticRemoteKey)
|
||||
switch {
|
||||
// If both sides signalled a tweakless channel, and the
|
||||
// resulting channel doesn't reflect this, then this
|
||||
// is a failed case.
|
||||
case tweaklessSignalled && !tweaklessChans:
|
||||
t.Fatalf("expected tweakless channnel, got " +
|
||||
"non-tweaked channel")
|
||||
|
||||
// If both sides didn't signal a tweakless
|
||||
// channel, and the resulting channel is
|
||||
// tweakless, and this is also a failed case.
|
||||
case !tweaklessSignalled && tweaklessChans:
|
||||
t.Fatalf("expected non-tweaked channel, got " +
|
||||
"tweakless channel")
|
||||
}
|
||||
|
||||
// As we've concluded this sub-test case we'll
|
||||
// now close out the channel for both sides.
|
||||
closeChan()
|
||||
})
|
||||
if !success {
|
||||
break test
|
||||
}
|
||||
|
||||
shutdownAndAssert(net, t, carol)
|
||||
shutdownAndAssert(net, t, dave)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
||||
|
@ -863,6 +863,12 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool,
|
||||
diskCommit *channeldb.ChannelCommitment, localCommitPoint,
|
||||
remoteCommitPoint *btcec.PublicKey) (*commitment, error) {
|
||||
|
||||
// If this commit is tweakless, then it'll affect the way we derive our
|
||||
// keys, which will affect the commitment transaction reconstruction.
|
||||
// So we'll determine this first, before we do anything else.
|
||||
tweaklessCommit := (lc.channelState.ChanType ==
|
||||
channeldb.SingleFunderTweakless)
|
||||
|
||||
// First, we'll need to re-derive the commitment key ring for each
|
||||
// party used within this particular state. If this is a pending commit
|
||||
// (we extended but weren't able to complete the commitment dance
|
||||
@ -870,15 +876,15 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool,
|
||||
// haven't yet received a responding commitment from the remote party.
|
||||
var localCommitKeys, remoteCommitKeys *CommitmentKeyRing
|
||||
if localCommitPoint != nil {
|
||||
localCommitKeys = deriveCommitmentKeys(
|
||||
localCommitPoint, true, lc.localChanCfg,
|
||||
lc.remoteChanCfg,
|
||||
localCommitKeys = DeriveCommitmentKeys(
|
||||
localCommitPoint, true, tweaklessCommit,
|
||||
lc.localChanCfg, lc.remoteChanCfg,
|
||||
)
|
||||
}
|
||||
if remoteCommitPoint != nil {
|
||||
remoteCommitKeys = deriveCommitmentKeys(
|
||||
remoteCommitPoint, false, lc.localChanCfg,
|
||||
lc.remoteChanCfg,
|
||||
remoteCommitKeys = DeriveCommitmentKeys(
|
||||
remoteCommitPoint, false, tweaklessCommit,
|
||||
lc.localChanCfg, lc.remoteChanCfg,
|
||||
)
|
||||
}
|
||||
|
||||
@ -975,10 +981,11 @@ type CommitmentKeyRing struct {
|
||||
RevocationKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// deriveCommitmentKey generates a new commitment key set using the base points
|
||||
// DeriveCommitmentKey generates a new commitment key set using the base points
|
||||
// and commitment point. The keys are derived differently depending whether the
|
||||
// commitment transaction is ours or the remote peer's.
|
||||
func deriveCommitmentKeys(commitPoint *btcec.PublicKey, isOurCommit bool,
|
||||
func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
|
||||
isOurCommit, tweaklessCommit bool,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing {
|
||||
|
||||
// First, we'll derive all the keys that don't depend on the context of
|
||||
@ -1023,11 +1030,20 @@ func deriveCommitmentKeys(commitPoint *btcec.PublicKey, isOurCommit bool,
|
||||
// With the base points assigned, we can now derive the actual keys
|
||||
// using the base point, and the current commitment tweak.
|
||||
keyRing.DelayKey = input.TweakPubKey(delayBasePoint, commitPoint)
|
||||
keyRing.NoDelayKey = input.TweakPubKey(noDelayBasePoint, commitPoint)
|
||||
keyRing.RevocationKey = input.DeriveRevocationPubkey(
|
||||
revocationBasePoint, commitPoint,
|
||||
)
|
||||
|
||||
// If this commitment should omit the tweak for the remote point, then
|
||||
// we'll use that directly, and ignore the commitPoint tweak.
|
||||
if tweaklessCommit {
|
||||
keyRing.NoDelayKey = noDelayBasePoint
|
||||
} else {
|
||||
keyRing.NoDelayKey = input.TweakPubKey(
|
||||
noDelayBasePoint, commitPoint,
|
||||
)
|
||||
}
|
||||
|
||||
return keyRing
|
||||
}
|
||||
|
||||
@ -1695,9 +1711,10 @@ func (lc *LightningChannel) restoreCommitState(
|
||||
|
||||
// We'll also re-create the set of commitment keys needed to
|
||||
// fully re-derive the state.
|
||||
pendingRemoteKeyChain = deriveCommitmentKeys(
|
||||
pendingCommitPoint, false, lc.localChanCfg,
|
||||
lc.remoteChanCfg,
|
||||
tweaklessCommit := lc.channelState.ChanType.IsTweakless()
|
||||
pendingRemoteKeyChain = DeriveCommitmentKeys(
|
||||
pendingCommitPoint, false, tweaklessCommit,
|
||||
lc.localChanCfg, lc.remoteChanCfg,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1981,8 +1998,11 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
|
||||
// With the commitment point generated, we can now generate the four
|
||||
// keys we'll need to reconstruct the commitment state,
|
||||
keyRing := deriveCommitmentKeys(commitmentPoint, false,
|
||||
&chanState.LocalChanCfg, &chanState.RemoteChanCfg)
|
||||
tweaklessCommit := chanState.ChanType.IsTweakless()
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitmentPoint, false, tweaklessCommit,
|
||||
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
|
||||
)
|
||||
|
||||
// Next, reconstruct the scripts as they were present at this state
|
||||
// number so we can have the proper witness script to sign and include
|
||||
@ -2045,6 +2065,12 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
|
||||
// If this is a tweakless commitment, then we can safely blank
|
||||
// out the SingleTweak value as it isn't needed.
|
||||
if tweaklessCommit {
|
||||
localSignDesc.SingleTweak = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, if the remote balance exceeds the remote party's dust
|
||||
@ -3042,6 +3068,146 @@ func (lc *LightningChannel) createCommitDiff(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateCommitmentSanity is used to validate the current state of the
|
||||
// commitment transaction in terms of the ChannelConstraints that we and our
|
||||
// remote peer agreed upon during the funding workflow. The predictAdded
|
||||
// parameter should be set to a valid PaymentDescriptor if we are validating
|
||||
// in the state when adding a new HTLC, or nil otherwise.
|
||||
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
||||
ourLogCounter uint64, remoteChain bool,
|
||||
predictAdded *PaymentDescriptor) error {
|
||||
|
||||
// Fetch all updates not committed.
|
||||
view := lc.fetchHTLCView(theirLogCounter, ourLogCounter)
|
||||
|
||||
// If we are checking if we can add a new HTLC, we add this to the
|
||||
// update log, in order to validate the sanity of the commitment
|
||||
// resulting from _actually adding_ this HTLC to the state.
|
||||
if predictAdded != nil {
|
||||
// If we are adding an HTLC, this will be an Add to the local
|
||||
// update log.
|
||||
view.ourUpdates = append(view.ourUpdates, predictAdded)
|
||||
}
|
||||
|
||||
commitChain := lc.localCommitChain
|
||||
if remoteChain {
|
||||
commitChain = lc.remoteCommitChain
|
||||
}
|
||||
ourInitialBalance := commitChain.tip().ourBalance
|
||||
theirInitialBalance := commitChain.tip().theirBalance
|
||||
|
||||
ourBalance, theirBalance, commitWeight, filteredView := lc.computeView(
|
||||
view, remoteChain, false,
|
||||
)
|
||||
feePerKw := filteredView.feePerKw
|
||||
|
||||
// Calculate the commitment fee, and subtract it from the initiator's
|
||||
// balance.
|
||||
commitFee := feePerKw.FeeForWeight(commitWeight)
|
||||
commitFeeMsat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
if lc.channelState.IsInitiator {
|
||||
ourBalance -= commitFeeMsat
|
||||
} else {
|
||||
theirBalance -= commitFeeMsat
|
||||
}
|
||||
|
||||
// As a quick sanity check, we'll ensure that if we interpret the
|
||||
// balances as signed integers, they haven't dipped down below zero. If
|
||||
// they have, then this indicates that a party doesn't have sufficient
|
||||
// balance to satisfy the final evaluated HTLC's.
|
||||
switch {
|
||||
case int64(ourBalance) < 0:
|
||||
return ErrBelowChanReserve
|
||||
case int64(theirBalance) < 0:
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
// Ensure that the fee being applied is enough to be relayed across the
|
||||
// network in a reasonable time frame.
|
||||
if feePerKw < FeePerKwFloor {
|
||||
return fmt.Errorf("commitment fee per kw %v below fee floor %v",
|
||||
feePerKw, FeePerKwFloor)
|
||||
}
|
||||
|
||||
// If the added HTLCs will decrease the balance, make sure they won't
|
||||
// dip the local and remote balances below the channel reserves.
|
||||
switch {
|
||||
case ourBalance < ourInitialBalance &&
|
||||
ourBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.localChanCfg.ChanReserve):
|
||||
|
||||
return ErrBelowChanReserve
|
||||
case theirBalance < theirInitialBalance &&
|
||||
theirBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.remoteChanCfg.ChanReserve):
|
||||
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
// validateUpdates take a set of updates, and validates them against
|
||||
// the passed channel constraints.
|
||||
validateUpdates := func(updates []*PaymentDescriptor,
|
||||
constraints *channeldb.ChannelConfig) error {
|
||||
|
||||
// We keep track of the number of HTLCs in flight for the
|
||||
// commitment, and the amount in flight.
|
||||
var numInFlight uint16
|
||||
var amtInFlight lnwire.MilliSatoshi
|
||||
|
||||
// Go through all updates, checking that they don't violate the
|
||||
// channel constraints.
|
||||
for _, entry := range updates {
|
||||
if entry.EntryType == Add {
|
||||
// An HTLC is being added, this will add to the
|
||||
// number and amount in flight.
|
||||
amtInFlight += entry.Amount
|
||||
numInFlight++
|
||||
|
||||
// Check that the value of the HTLC they added
|
||||
// is above our minimum.
|
||||
if entry.Amount < constraints.MinHTLC {
|
||||
return ErrBelowMinHTLC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know the total value of added HTLCs, we check
|
||||
// that this satisfy the MaxPendingAmont contraint.
|
||||
if amtInFlight > constraints.MaxPendingAmount {
|
||||
return ErrMaxPendingAmount
|
||||
}
|
||||
|
||||
// In this step, we verify that the total number of active
|
||||
// HTLCs does not exceed the constraint of the maximum number
|
||||
// of HTLCs in flight.
|
||||
if numInFlight > constraints.MaxAcceptedHtlcs {
|
||||
return ErrMaxHTLCNumber
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// First check that the remote updates won't violate it's channel
|
||||
// constraints.
|
||||
err := validateUpdates(
|
||||
filteredView.theirUpdates, lc.remoteChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Secondly check that our updates won't violate our channel
|
||||
// constraints.
|
||||
err = validateUpdates(
|
||||
filteredView.ourUpdates, lc.localChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignNextCommitment signs a new commitment which includes any previous
|
||||
// unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs
|
||||
// committed in previous commitment updates. Signing a new commitment
|
||||
@ -3091,8 +3257,9 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch
|
||||
// Grab the next commitment point for the remote party. This will be
|
||||
// used within fetchCommitmentView to derive all the keys necessary to
|
||||
// construct the commitment state.
|
||||
keyRing := deriveCommitmentKeys(
|
||||
commitPoint, false, lc.localChanCfg, lc.remoteChanCfg,
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitPoint, false, lc.channelState.ChanType.IsTweakless(),
|
||||
lc.localChanCfg, lc.remoteChanCfg,
|
||||
)
|
||||
|
||||
// Create a new commitment view which will calculate the evaluated
|
||||
@ -3495,7 +3662,10 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
||||
commitPoint = lc.channelState.RemoteNextRevocation
|
||||
}
|
||||
|
||||
if commitPoint != nil &&
|
||||
// Only if this is a tweakless channel will we attempt to verify the
|
||||
// commitment point, as otherwise it has no validity requirements.
|
||||
tweakless := lc.channelState.ChanType.IsTweakless()
|
||||
if !tweakless && commitPoint != nil &&
|
||||
!commitPoint.IsEqual(msg.LocalUnrevokedCommitPoint) {
|
||||
|
||||
walletLog.Errorf("ChannelPoint(%v), sync failed: remote "+
|
||||
@ -3585,138 +3755,6 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView
|
||||
}
|
||||
|
||||
// validateCommitmentSanity is used to validate the current state of the
|
||||
// commitment transaction in terms of the ChannelConstraints that we and our
|
||||
// remote peer agreed upon during the funding workflow. The predictAdded
|
||||
// parameter should be set to a valid PaymentDescriptor if we are validating
|
||||
// in the state when adding a new HTLC, or nil otherwise.
|
||||
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
||||
ourLogCounter uint64, remoteChain bool,
|
||||
predictAdded *PaymentDescriptor) error {
|
||||
|
||||
// Fetch all updates not committed.
|
||||
view := lc.fetchHTLCView(theirLogCounter, ourLogCounter)
|
||||
|
||||
// If we are checking if we can add a new HTLC, we add this to the
|
||||
// update log, in order to validate the sanity of the commitment
|
||||
// resulting from _actually adding_ this HTLC to the state.
|
||||
if predictAdded != nil {
|
||||
// If we are adding an HTLC, this will be an Add to the local
|
||||
// update log.
|
||||
view.ourUpdates = append(view.ourUpdates, predictAdded)
|
||||
}
|
||||
|
||||
commitChain := lc.localCommitChain
|
||||
if remoteChain {
|
||||
commitChain = lc.remoteCommitChain
|
||||
}
|
||||
ourInitialBalance := commitChain.tip().ourBalance
|
||||
theirInitialBalance := commitChain.tip().theirBalance
|
||||
|
||||
ourBalance, theirBalance, commitWeight, filteredView := lc.computeView(
|
||||
view, remoteChain, false,
|
||||
)
|
||||
feePerKw := filteredView.feePerKw
|
||||
|
||||
// Calculate the commitment fee, and subtract it from the initiator's
|
||||
// balance.
|
||||
commitFee := feePerKw.FeeForWeight(commitWeight)
|
||||
commitFeeMsat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
if lc.channelState.IsInitiator {
|
||||
ourBalance -= commitFeeMsat
|
||||
} else {
|
||||
theirBalance -= commitFeeMsat
|
||||
}
|
||||
|
||||
// As a quick sanity check, we'll ensure that if we interpret the
|
||||
// balances as signed integers, they haven't dipped down below zero. If
|
||||
// they have, then this indicates that a party doesn't have sufficient
|
||||
// balance to satisfy the final evaluated HTLC's.
|
||||
switch {
|
||||
case int64(ourBalance) < 0:
|
||||
return ErrBelowChanReserve
|
||||
case int64(theirBalance) < 0:
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
// If the added HTLCs will decrease the balance, make sure they won't
|
||||
// dip the local and remote balances below the channel reserves.
|
||||
if ourBalance < ourInitialBalance &&
|
||||
ourBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.localChanCfg.ChanReserve) {
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
if theirBalance < theirInitialBalance &&
|
||||
theirBalance < lnwire.NewMSatFromSatoshis(
|
||||
lc.remoteChanCfg.ChanReserve) {
|
||||
return ErrBelowChanReserve
|
||||
}
|
||||
|
||||
// validateUpdates take a set of updates, and validates them against
|
||||
// the passed channel constraints.
|
||||
validateUpdates := func(updates []*PaymentDescriptor,
|
||||
constraints *channeldb.ChannelConfig) error {
|
||||
|
||||
// We keep track of the number of HTLCs in flight for the
|
||||
// commitment, and the amount in flight.
|
||||
var numInFlight uint16
|
||||
var amtInFlight lnwire.MilliSatoshi
|
||||
|
||||
// Go through all updates, checking that they don't violate the
|
||||
// channel constraints.
|
||||
for _, entry := range updates {
|
||||
if entry.EntryType == Add {
|
||||
// An HTLC is being added, this will add to the
|
||||
// number and amount in flight.
|
||||
amtInFlight += entry.Amount
|
||||
numInFlight++
|
||||
|
||||
// Check that the value of the HTLC they added
|
||||
// is above our minimum.
|
||||
if entry.Amount < constraints.MinHTLC {
|
||||
return ErrBelowMinHTLC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know the total value of added HTLCs, we check
|
||||
// that this satisfy the MaxPendingAmont contraint.
|
||||
if amtInFlight > constraints.MaxPendingAmount {
|
||||
return ErrMaxPendingAmount
|
||||
}
|
||||
|
||||
// In this step, we verify that the total number of active
|
||||
// HTLCs does not exceed the constraint of the maximum number
|
||||
// of HTLCs in flight.
|
||||
if numInFlight > constraints.MaxAcceptedHtlcs {
|
||||
return ErrMaxHTLCNumber
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// First check that the remote updates won't violate it's channel
|
||||
// constraints.
|
||||
err := validateUpdates(
|
||||
filteredView.theirUpdates, lc.remoteChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Secondly check that our updates won't violate our channel
|
||||
// constraints.
|
||||
err = validateUpdates(
|
||||
filteredView.ourUpdates, lc.localChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genHtlcSigValidationJobs generates a series of signatures verification jobs
|
||||
// meant to verify all the signatures for HTLC's attached to a newly created
|
||||
// commitment state. The jobs generated are fully populated, and can be sent
|
||||
@ -3976,8 +4014,9 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
||||
return err
|
||||
}
|
||||
commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
|
||||
keyRing := deriveCommitmentKeys(
|
||||
commitPoint, true, lc.localChanCfg, lc.remoteChanCfg,
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitPoint, true, lc.channelState.ChanType.IsTweakless(),
|
||||
lc.localChanCfg, lc.remoteChanCfg,
|
||||
)
|
||||
|
||||
// With the current commitment point re-calculated, construct the new
|
||||
@ -5006,8 +5045,9 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
|
||||
// First, we'll generate the commitment point and the revocation point
|
||||
// so we can re-construct the HTLC state and also our payment key.
|
||||
keyRing := deriveCommitmentKeys(
|
||||
commitPoint, false, &chanState.LocalChanCfg,
|
||||
tweaklessCommit := chanState.ChanType.IsTweakless()
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitPoint, false, tweaklessCommit, &chanState.LocalChanCfg,
|
||||
&chanState.RemoteChanCfg,
|
||||
)
|
||||
|
||||
@ -5070,6 +5110,12 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
},
|
||||
MaturityDelay: 0,
|
||||
}
|
||||
|
||||
// If this is a tweakless commitment, then we can safely blank
|
||||
// out the SingleTweak value as it isn't needed.
|
||||
if tweaklessCommit {
|
||||
commitResolution.SelfOutputSignDesc.SingleTweak = nil
|
||||
}
|
||||
}
|
||||
|
||||
closeSummary := channeldb.ChannelCloseSummary{
|
||||
@ -5655,8 +5701,10 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
return nil, err
|
||||
}
|
||||
commitPoint := input.ComputeCommitmentPoint(revocation[:])
|
||||
keyRing := deriveCommitmentKeys(commitPoint, true, &chanState.LocalChanCfg,
|
||||
&chanState.RemoteChanCfg)
|
||||
keyRing := DeriveCommitmentKeys(
|
||||
commitPoint, true, chanState.ChanType.IsTweakless(),
|
||||
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
|
||||
)
|
||||
selfScript, err := input.CommitScriptToSelf(csvTimeout, keyRing.DelayKey,
|
||||
keyRing.RevocationKey)
|
||||
if err != nil {
|
||||
|
@ -52,22 +52,13 @@ func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx,
|
||||
spew.Sdump(commitTx))
|
||||
}
|
||||
|
||||
// TestSimpleAddSettleWorkflow tests a simple channel scenario wherein the
|
||||
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
|
||||
// this change, then bob immediately commits a settlement of the HTLC after the
|
||||
// initial add is fully committed in both commit chains.
|
||||
//
|
||||
// TODO(roasbeef): write higher level framework to exercise various states of
|
||||
// the state machine
|
||||
// * DSL language perhaps?
|
||||
// * constructed via input/output files
|
||||
func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// testAddSettleWorkflow tests a simple channel scenario where Alice and Bob
|
||||
// add, the settle an HTLC between themselves.
|
||||
func testAddSettleWorkflow(t *testing.T, tweakless bool) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(tweakless)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -295,10 +286,10 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
"instead forwarding: %v", len(fwdPkg.SettleFails))
|
||||
}
|
||||
|
||||
// At this point, Bob should have 6 BTC settled, with Alice still having
|
||||
// 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
|
||||
// should show 1 BTC received. They should also be at commitment height
|
||||
// two, with the revocation window extended by 1 (5).
|
||||
// At this point, Bob should have 6 BTC settled, with Alice still
|
||||
// having 4 BTC. Alice's channel should show 1 BTC sent and Bob's
|
||||
// channel should show 1 BTC received. They should also be at
|
||||
// commitment height two, with the revocation window extended by 1 (5).
|
||||
mSatTransferred := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||
if aliceChannel.channelState.TotalMSatSent != mSatTransferred {
|
||||
t.Fatalf("alice satoshis sent incorrect %v vs %v expected",
|
||||
@ -348,6 +339,26 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSimpleAddSettleWorkflow tests a simple channel scenario wherein the
|
||||
// local node (Alice in this case) creates a new outgoing HTLC to bob, commits
|
||||
// this change, then bob immediately commits a settlement of the HTLC after the
|
||||
// initial add is fully committed in both commit chains.
|
||||
//
|
||||
// TODO(roasbeef): write higher level framework to exercise various states of
|
||||
// the state machine
|
||||
// * DSL language perhaps?
|
||||
// * constructed via input/output files
|
||||
func TestSimpleAddSettleWorkflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tweakless := range []bool{true, false} {
|
||||
tweakless := tweakless
|
||||
t.Run(fmt.Sprintf("tweakless=%v", tweakless), func(t *testing.T) {
|
||||
testAddSettleWorkflow(t, tweakless)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckCommitTxSize checks that estimation size of commitment
|
||||
// transaction with some degree of error corresponds to the actual size.
|
||||
func TestCheckCommitTxSize(t *testing.T) {
|
||||
@ -377,7 +388,7 @@ func TestCheckCommitTxSize(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -436,7 +447,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -473,7 +484,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
||||
// transaction is well formed, and the signatures verify.
|
||||
aliceCloseTx, _, err := bobChannel.CompleteCooperativeClose(
|
||||
bobCloseSig, aliceCloseSig, bobDeliveryScript,
|
||||
aliceDeliveryScript, bobFee)
|
||||
aliceDeliveryScript, bobFee,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to complete alice cooperative close: %v", err)
|
||||
}
|
||||
@ -481,7 +493,8 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
||||
|
||||
bobCloseTx, _, err := aliceChannel.CompleteCooperativeClose(
|
||||
aliceCloseSig, bobCloseSig, aliceDeliveryScript,
|
||||
bobDeliveryScript, aliceFee)
|
||||
bobDeliveryScript, aliceFee,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to complete bob cooperative close: %v", err)
|
||||
}
|
||||
@ -503,7 +516,7 @@ func TestForceClose(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -787,7 +800,7 @@ func TestForceCloseDustOutput(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -905,7 +918,7 @@ func TestDustHTLCFees(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -982,7 +995,7 @@ func TestHTLCDustLimit(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1067,7 +1080,7 @@ func TestHTLCSigNumber(t *testing.T) {
|
||||
// Create a test channel funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC. Alice's dustlimit is 200 sat, while
|
||||
// Bob has 1300 sat.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1237,7 +1250,7 @@ func TestChannelBalanceDustLimit(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1305,7 +1318,7 @@ func TestStateUpdatePersistence(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1353,7 +1366,7 @@ func TestStateUpdatePersistence(t *testing.T) {
|
||||
}
|
||||
|
||||
// Also add a fee update to the update logs.
|
||||
fee := SatPerKWeight(111)
|
||||
fee := SatPerKWeight(333)
|
||||
if err := aliceChannel.UpdateFee(fee); err != nil {
|
||||
t.Fatalf("unable to send fee update")
|
||||
}
|
||||
@ -1646,7 +1659,7 @@ func TestCancelHTLC(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1760,7 +1773,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1921,7 +1934,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
|
||||
func TestUpdateFeeAdjustments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1959,7 +1972,7 @@ func TestUpdateFeeAdjustments(t *testing.T) {
|
||||
// Finally, we'll attempt to adjust the fee down and use a fee which is
|
||||
// smaller than the initial base fee rate. The fee application and
|
||||
// state transition should proceed without issue.
|
||||
newFee = SatPerKWeight(baseFeeRate / 100)
|
||||
newFee = SatPerKWeight(baseFeeRate / 10)
|
||||
if err := aliceChannel.UpdateFee(newFee); err != nil {
|
||||
t.Fatalf("unable to alice update fee: %v", err)
|
||||
}
|
||||
@ -1976,7 +1989,7 @@ func TestUpdateFeeAdjustments(t *testing.T) {
|
||||
func TestUpdateFeeFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -1984,7 +1997,9 @@ func TestUpdateFeeFail(t *testing.T) {
|
||||
|
||||
// Bob receives the update, that will apply to his commitment
|
||||
// transaction.
|
||||
bobChannel.ReceiveUpdateFee(111)
|
||||
if err := bobChannel.ReceiveUpdateFee(333); err != nil {
|
||||
t.Fatalf("unable to apply fee update: %v", err)
|
||||
}
|
||||
|
||||
// Alice sends signature for commitment that does not cover any fee
|
||||
// update.
|
||||
@ -2008,7 +2023,7 @@ func TestUpdateFeeFail(t *testing.T) {
|
||||
func TestUpdateFeeConcurrentSig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2033,7 +2048,7 @@ func TestUpdateFeeConcurrentSig(t *testing.T) {
|
||||
}
|
||||
|
||||
// Simulate Alice sending update fee message to bob.
|
||||
fee := SatPerKWeight(111)
|
||||
fee := SatPerKWeight(333)
|
||||
if err := aliceChannel.UpdateFee(fee); err != nil {
|
||||
t.Fatalf("unable to send fee update")
|
||||
}
|
||||
@ -2094,7 +2109,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2119,7 +2134,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
// Simulate Alice sending update fee message to bob.
|
||||
fee := SatPerKWeight(111)
|
||||
fee := SatPerKWeight(333)
|
||||
aliceChannel.UpdateFee(fee)
|
||||
bobChannel.ReceiveUpdateFee(fee)
|
||||
|
||||
@ -2208,7 +2223,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2233,7 +2248,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
// Simulate Alice sending update fee message to bob
|
||||
fee := SatPerKWeight(111)
|
||||
fee := SatPerKWeight(333)
|
||||
aliceChannel.UpdateFee(fee)
|
||||
bobChannel.ReceiveUpdateFee(fee)
|
||||
|
||||
@ -2349,7 +2364,7 @@ func TestUpdateFeeReceiverSendsUpdate(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2357,7 +2372,7 @@ func TestUpdateFeeReceiverSendsUpdate(t *testing.T) {
|
||||
|
||||
// Since Alice is the channel initiator, she should fail when receiving
|
||||
// fee update
|
||||
fee := SatPerKWeight(111)
|
||||
fee := SatPerKWeight(333)
|
||||
err = aliceChannel.ReceiveUpdateFee(fee)
|
||||
if err == nil {
|
||||
t.Fatalf("expected alice to fail receiving fee update")
|
||||
@ -2378,15 +2393,15 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
// Simulate Alice sending update fee message to bob.
|
||||
fee1 := SatPerKWeight(111)
|
||||
fee2 := SatPerKWeight(222)
|
||||
fee1 := SatPerKWeight(333)
|
||||
fee2 := SatPerKWeight(333)
|
||||
fee := SatPerKWeight(333)
|
||||
aliceChannel.UpdateFee(fee1)
|
||||
aliceChannel.UpdateFee(fee2)
|
||||
@ -2490,7 +2505,7 @@ func TestAddHTLCNegativeBalance(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2571,7 +2586,7 @@ func TestChanSyncFullySynced(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -2691,7 +2706,7 @@ func TestChanSyncOweCommitment(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3005,7 +3020,7 @@ func TestChanSyncOweRevocation(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3195,7 +3210,7 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3364,7 +3379,7 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3593,7 +3608,7 @@ func TestChanSyncFailure(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3846,7 +3861,7 @@ func TestFeeUpdateRejectInsaneFee(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -3872,7 +3887,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4055,7 +4070,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4274,7 +4289,7 @@ func TestChanSyncUnableToSync(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4311,7 +4326,7 @@ func TestChanSyncInvalidLastSecret(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4401,7 +4416,7 @@ func TestChanAvailableBandwidth(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4524,7 +4539,7 @@ func TestSignCommitmentFailNotLockedIn(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4549,7 +4564,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll make a channel between Alice and Bob.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4863,7 +4878,7 @@ func TestInvalidCommitSigError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll make a channel between Alice and Bob.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -4910,7 +4925,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5068,7 +5083,9 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
||||
// Create a test channel which will be used for the duration of this
|
||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||
// and Bob having 5 BTC.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5166,7 +5183,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
||||
})
|
||||
aliceSignDesc.SigHashes = txscript.NewTxSigHashes(sweepTx)
|
||||
sweepTx.TxIn[0].Witness, err = input.CommitSpendNoDelay(
|
||||
aliceChannel.Signer, &aliceSignDesc, sweepTx,
|
||||
aliceChannel.Signer, &aliceSignDesc, sweepTx, false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate sweep witness: %v", err)
|
||||
@ -5175,9 +5192,9 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) {
|
||||
// If we validate the signature on the new sweep transaction, it should
|
||||
// be fully valid.
|
||||
vm, err := txscript.NewEngine(
|
||||
aliceSignDesc.Output.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, aliceSignDesc.Output.Value,
|
||||
aliceSignDesc.Output.PkScript, sweepTx, 0,
|
||||
txscript.StandardVerifyFlags, nil, nil,
|
||||
aliceSignDesc.Output.Value,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
@ -5194,7 +5211,7 @@ func TestDesyncHTLCs(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5261,7 +5278,7 @@ func TestMaxAcceptedHTLCs(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5322,7 +5339,7 @@ func TestMaxPendingAmount(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5410,7 +5427,9 @@ func TestChanReserve(t *testing.T) {
|
||||
setupChannels := func() (*LightningChannel, *LightningChannel, func()) {
|
||||
// We'll kick off the test by creating our channels which both
|
||||
// are loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5626,7 +5645,7 @@ func TestMinHTLC(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5683,7 +5702,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) {
|
||||
|
||||
// We'll kick off the test by creating our channels which both are
|
||||
// loaded with 5 BTC each.
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -5855,7 +5874,7 @@ func compareLogs(a, b *updateLog) error {
|
||||
func TestChannelRestoreUpdateLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6024,7 +6043,7 @@ func restoreAndAssert(t *testing.T, channel *LightningChannel, numAddsLocal,
|
||||
func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6145,7 +6164,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) {
|
||||
func TestDuplicateFailRejection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6223,7 +6242,7 @@ func TestDuplicateFailRejection(t *testing.T) {
|
||||
func TestDuplicateSettleRejection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6304,7 +6323,7 @@ func TestDuplicateSettleRejection(t *testing.T) {
|
||||
func TestChannelRestoreCommitHeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6491,7 +6510,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) {
|
||||
func TestForceCloseFailLocalDataLoss(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
@ -6522,7 +6541,7 @@ func TestForceCloseFailLocalDataLoss(t *testing.T) {
|
||||
func TestForceCloseBorkedState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(false)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ func testCancelNonExistentReservation(miner *rpctest.Harness,
|
||||
// Create our own reservation, give it some ID.
|
||||
res, err := lnwallet.NewChannelReservation(
|
||||
10000, 10000, feePerKw, alice, 22, 10, &testHdSeed,
|
||||
lnwire.FFAnnounceChannel,
|
||||
lnwire.FFAnnounceChannel, true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create res: %v", err)
|
||||
@ -736,6 +736,7 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
|
||||
FundingFeePerKw: 1000,
|
||||
PushMSat: 0,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: true,
|
||||
}
|
||||
_, err = alice.InitChannelReservation(req)
|
||||
switch {
|
||||
@ -790,7 +791,7 @@ func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContributi
|
||||
}
|
||||
|
||||
func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
alice, bob *lnwallet.LightningWallet, t *testing.T) {
|
||||
alice, bob *lnwallet.LightningWallet, t *testing.T, tweakless bool) {
|
||||
|
||||
// For this scenario, Alice will be the channel initiator while bob
|
||||
// will act as the responder to the workflow.
|
||||
@ -817,6 +818,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
FundingFeePerKw: feePerKw,
|
||||
PushMSat: pushAmt,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: tweakless,
|
||||
}
|
||||
aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
|
||||
if err != nil {
|
||||
@ -860,6 +862,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
FundingFeePerKw: feePerKw,
|
||||
PushMSat: pushAmt,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: tweakless,
|
||||
}
|
||||
bobChanReservation, err := bob.InitChannelReservation(bobReq)
|
||||
if err != nil {
|
||||
@ -966,7 +969,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
if !aliceChannels[0].IsInitiator {
|
||||
t.Fatalf("alice not detected as channel initiator")
|
||||
}
|
||||
if aliceChannels[0].ChanType != channeldb.SingleFunder {
|
||||
if !aliceChannels[0].ChanType.IsSingleFunder() {
|
||||
t.Fatalf("channel type is incorrect, expected %v instead got %v",
|
||||
channeldb.SingleFunder, aliceChannels[0].ChanType)
|
||||
}
|
||||
@ -986,7 +989,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
if bobChannels[0].IsInitiator {
|
||||
t.Fatalf("bob not detected as channel responder")
|
||||
}
|
||||
if bobChannels[0].ChanType != channeldb.SingleFunder {
|
||||
if !bobChannels[0].ChanType.IsSingleFunder() {
|
||||
t.Fatalf("channel type is incorrect, expected %v instead got %v",
|
||||
channeldb.SingleFunder, bobChannels[0].ChanType)
|
||||
}
|
||||
@ -2515,7 +2518,23 @@ var walletTests = []walletTestCase{
|
||||
},
|
||||
{
|
||||
name: "single funding workflow",
|
||||
test: testSingleFunderReservationWorkflow,
|
||||
test: func(miner *rpctest.Harness, alice,
|
||||
bob *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
testSingleFunderReservationWorkflow(
|
||||
miner, alice, bob, t, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single funding workflow tweakless",
|
||||
test: func(miner *rpctest.Harness, alice,
|
||||
bob *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
testSingleFunderReservationWorkflow(
|
||||
miner, alice, bob, t, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dual funder workflow",
|
||||
|
@ -130,7 +130,8 @@ type ChannelReservation struct {
|
||||
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
commitFeePerKw SatPerKWeight, wallet *LightningWallet,
|
||||
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
||||
flags lnwire.FundingFlag) (*ChannelReservation, error) {
|
||||
flags lnwire.FundingFlag,
|
||||
tweaklessCommit bool) (*ChannelReservation, error) {
|
||||
|
||||
var (
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
@ -140,7 +141,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
|
||||
commitFee := commitFeePerKw.FeeForWeight(input.CommitWeight)
|
||||
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
|
||||
// TODO(halseth): make method take remote funding amount direcly
|
||||
// TODO(halseth): make method take remote funding amount directly
|
||||
// instead of inferring it from capacity and local amt.
|
||||
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
||||
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
@ -213,7 +214,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// non-zero push amt (there's no pushing for dual funder), then this is
|
||||
// a single-funder channel.
|
||||
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
|
||||
chanType = channeldb.SingleFunder
|
||||
if tweaklessCommit {
|
||||
chanType = channeldb.SingleFunderTweakless
|
||||
} else {
|
||||
chanType = channeldb.SingleFunder
|
||||
}
|
||||
} else {
|
||||
// Otherwise, this is a dual funder channel, and no side is
|
||||
// technically the "initiator"
|
||||
|
@ -88,8 +88,11 @@ var (
|
||||
// allocated to each side. Within the channel, Alice is the initiator. The
|
||||
// function also returns a "cleanup" function that is meant to be called once
|
||||
// the test has been finalized. The clean up function will remote all temporary
|
||||
// files created
|
||||
func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error) {
|
||||
// files created. If tweaklessCommits is true, then the commits within the
|
||||
// channels will use the new format, otherwise the legacy format.
|
||||
func CreateTestChannels(tweaklessCommits bool) (
|
||||
*LightningChannel, *LightningChannel, func(), error) {
|
||||
|
||||
channelCapacity, err := btcutil.NewAmount(10)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
@ -202,9 +205,10 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
||||
}
|
||||
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
||||
|
||||
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal,
|
||||
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
|
||||
*fundingTxIn)
|
||||
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(
|
||||
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
||||
bobCommitPoint, *fundingTxIn, tweaklessCommits,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -270,7 +274,7 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
||||
IdentityPub: aliceKeys[0].PubKey(),
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: true,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: bobCommitPoint,
|
||||
@ -288,7 +292,7 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
||||
IdentityPub: bobKeys[0].PubKey(),
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: false,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: aliceCommitPoint,
|
||||
@ -300,6 +304,11 @@ func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error)
|
||||
Packager: channeldb.NewChannelPackager(shortChanID),
|
||||
}
|
||||
|
||||
if !tweaklessCommits {
|
||||
aliceChannelState.ChanType = channeldb.SingleFunder
|
||||
bobChannelState.ChanType = channeldb.SingleFunder
|
||||
}
|
||||
|
||||
aliceSigner := &input.MockSigner{Privkeys: aliceKeys}
|
||||
bobSigner := &input.MockSigner{Privkeys: bobKeys}
|
||||
|
||||
|
@ -370,7 +370,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
|
||||
|
||||
// Manually construct a new LightningChannel.
|
||||
channelState := channeldb.OpenChannel{
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
ChainHash: *tc.netParams.GenesisHash,
|
||||
FundingOutpoint: tc.fundingOutpoint,
|
||||
ShortChannelID: tc.shortChanID,
|
||||
@ -999,18 +999,9 @@ func TestCommitTxStateHint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitmentSpendValidation test the spendability of both outputs within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// The following spending cases are covered by this test:
|
||||
// * Alice's spend from the delayed output on her commitment transaction.
|
||||
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
|
||||
// commitment transaction.
|
||||
// * Bob's spend from his unencumbered output within Alice's commitment
|
||||
// transaction.
|
||||
func TestCommitmentSpendValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// testSpendValidation ensures that we're able to spend all outputs in the
|
||||
// commitment transaction that we create.
|
||||
func testSpendValidation(t *testing.T, tweakless bool) {
|
||||
// We generate a fake output, and the corresponding txin. This output
|
||||
// doesn't need to exist, as we'll only be validating spending from the
|
||||
// transaction that references this.
|
||||
@ -1030,18 +1021,28 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
// We also set up set some resources for the commitment transaction.
|
||||
// Each side currently has 1 BTC within the channel, with a total
|
||||
// channel capacity of 2BTC.
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
testWalletPrivKey)
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
bobsPrivKey)
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), testWalletPrivKey,
|
||||
)
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), bobsPrivKey,
|
||||
)
|
||||
|
||||
revocationPreimage := testHdSeed.CloneBytes()
|
||||
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
revocationPreimage)
|
||||
commitSecret, commitPoint := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), revocationPreimage,
|
||||
)
|
||||
revokePubKey := input.DeriveRevocationPubkey(bobKeyPub, commitPoint)
|
||||
|
||||
aliceDelayKey := input.TweakPubKey(aliceKeyPub, commitPoint)
|
||||
|
||||
// Bob will have the channel "force closed" on him, so for the sake of
|
||||
// our commitments, if it's tweakless, his key will just be his regular
|
||||
// pubkey.
|
||||
bobPayKey := input.TweakPubKey(bobKeyPub, commitPoint)
|
||||
if tweakless {
|
||||
bobPayKey = bobKeyPub
|
||||
}
|
||||
|
||||
aliceCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub)
|
||||
bobCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub)
|
||||
@ -1062,8 +1063,10 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
RevocationKey: revokePubKey,
|
||||
NoDelayKey: bobPayKey,
|
||||
}
|
||||
commitmentTx, err := CreateCommitTx(*fakeFundingTxIn, keyRing, csvTimeout,
|
||||
channelBalance, channelBalance, DefaultDustLimit())
|
||||
commitmentTx, err := CreateCommitTx(
|
||||
*fakeFundingTxIn, keyRing, csvTimeout, channelBalance,
|
||||
channelBalance, DefaultDustLimit(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||
}
|
||||
@ -1088,8 +1091,9 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
})
|
||||
|
||||
// First, we'll test spending with Alice's key after the timeout.
|
||||
delayScript, err := input.CommitScriptToSelf(csvTimeout, aliceDelayKey,
|
||||
revokePubKey)
|
||||
delayScript, err := input.CommitScriptToSelf(
|
||||
csvTimeout, aliceDelayKey, revokePubKey,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate alice delay script: %v", err)
|
||||
}
|
||||
@ -1107,8 +1111,9 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
aliceWitnessSpend, err := input.CommitSpendTimeout(aliceSelfOutputSigner,
|
||||
signDesc, sweepTx)
|
||||
aliceWitnessSpend, err := input.CommitSpendTimeout(
|
||||
aliceSelfOutputSigner, signDesc, sweepTx,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate delay commit spend witness: %v", err)
|
||||
}
|
||||
@ -1177,7 +1182,6 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
SingleTweak: bobCommitTweak,
|
||||
WitnessScript: bobScriptP2WKH,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
@ -1187,15 +1191,21 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
bobRegularSpend, err := input.CommitSpendNoDelay(bobSigner, signDesc,
|
||||
sweepTx)
|
||||
if !tweakless {
|
||||
signDesc.SingleTweak = bobCommitTweak
|
||||
}
|
||||
bobRegularSpend, err := input.CommitSpendNoDelay(
|
||||
bobSigner, signDesc, sweepTx, tweakless,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob regular spend: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = bobRegularSpend
|
||||
vm, err = txscript.NewEngine(regularOutput.PkScript,
|
||||
vm, err = txscript.NewEngine(
|
||||
regularOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
nil, int64(channelBalance),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
@ -1203,3 +1213,26 @@ func TestCommitmentSpendValidation(t *testing.T) {
|
||||
t.Fatalf("bob p2wkh spend is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitmentSpendValidation test the spendability of both outputs within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// The following spending cases are covered by this test:
|
||||
// * Alice's spend from the delayed output on her commitment transaction.
|
||||
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
|
||||
// commitment transaction.
|
||||
// * Bob's spend from his unencumbered output within Alice's commitment
|
||||
// transaction.
|
||||
func TestCommitmentSpendValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// In the modern network, all channels use the new tweakless format,
|
||||
// but we also need to support older nodes that want to open channels
|
||||
// with the legacy format, so we'll test spending in both scenarios.
|
||||
for _, tweakless := range []bool{true, false} {
|
||||
tweakless := tweakless
|
||||
t.Run(fmt.Sprintf("tweak=%v", tweakless), func(t *testing.T) {
|
||||
testSpendValidation(t, tweakless)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,10 @@ type InitFundingReserveMsg struct {
|
||||
// output selected to fund the channel should satisfy.
|
||||
MinConfs int32
|
||||
|
||||
// Tweakless indicates if the channel should use the new tweakless
|
||||
// commitment format or not.
|
||||
Tweakless bool
|
||||
|
||||
// err is a channel in which all errors will be sent across. Will be
|
||||
// nil if this initial set is successful.
|
||||
//
|
||||
@ -489,6 +493,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||
reservation, err := NewChannelReservation(
|
||||
capacity, localFundingAmt, req.CommitFeePerKw, l, id,
|
||||
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
|
||||
req.Tweakless,
|
||||
)
|
||||
if err != nil {
|
||||
selected.unlockCoins()
|
||||
@ -655,12 +660,17 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
|
||||
func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
||||
ourChanCfg, theirChanCfg *channeldb.ChannelConfig,
|
||||
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
|
||||
fundingTxIn wire.TxIn) (*wire.MsgTx, *wire.MsgTx, error) {
|
||||
fundingTxIn wire.TxIn,
|
||||
tweaklessCommit bool) (*wire.MsgTx, *wire.MsgTx, error) {
|
||||
|
||||
localCommitmentKeys := deriveCommitmentKeys(localCommitPoint, true,
|
||||
ourChanCfg, theirChanCfg)
|
||||
remoteCommitmentKeys := deriveCommitmentKeys(remoteCommitPoint, false,
|
||||
ourChanCfg, theirChanCfg)
|
||||
localCommitmentKeys := DeriveCommitmentKeys(
|
||||
localCommitPoint, true, tweaklessCommit, ourChanCfg,
|
||||
theirChanCfg,
|
||||
)
|
||||
remoteCommitmentKeys := DeriveCommitmentKeys(
|
||||
remoteCommitPoint, false, tweaklessCommit, ourChanCfg,
|
||||
theirChanCfg,
|
||||
)
|
||||
|
||||
ourCommitTx, err := CreateCommitTx(fundingTxIn, localCommitmentKeys,
|
||||
uint32(ourChanCfg.CsvDelay), localBalance, remoteBalance,
|
||||
@ -827,11 +837,13 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
// With the funding tx complete, create both commitment transactions.
|
||||
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
|
||||
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
|
||||
tweaklessCommits := pendingReservation.partialState.ChanType.IsTweakless()
|
||||
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
|
||||
localBalance, remoteBalance, ourContribution.ChannelConfig,
|
||||
theirContribution.ChannelConfig,
|
||||
ourContribution.FirstCommitmentPoint,
|
||||
theirContribution.FirstCommitmentPoint, fundingTxIn,
|
||||
tweaklessCommits,
|
||||
)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
@ -842,7 +854,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
|
||||
// obfuscator then use it to encode the current state number within
|
||||
// both commitment transactions.
|
||||
var stateObfuscator [StateHintSize]byte
|
||||
if chanState.ChanType == channeldb.SingleFunder {
|
||||
if chanState.ChanType.IsSingleFunder() {
|
||||
stateObfuscator = DeriveStateHintObfuscator(
|
||||
ourContribution.PaymentBasePoint.PubKey,
|
||||
theirContribution.PaymentBasePoint.PubKey,
|
||||
@ -1151,13 +1163,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
|
||||
// remote node's commitment transactions.
|
||||
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
|
||||
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
|
||||
tweaklessCommits := pendingReservation.partialState.ChanType.IsTweakless()
|
||||
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
|
||||
localBalance, remoteBalance,
|
||||
pendingReservation.ourContribution.ChannelConfig,
|
||||
pendingReservation.theirContribution.ChannelConfig,
|
||||
pendingReservation.ourContribution.FirstCommitmentPoint,
|
||||
pendingReservation.theirContribution.FirstCommitmentPoint,
|
||||
*fundingTxIn,
|
||||
*fundingTxIn, tweaklessCommits,
|
||||
)
|
||||
if err != nil {
|
||||
req.err <- err
|
||||
|
@ -55,6 +55,16 @@ const (
|
||||
// packet.
|
||||
TLVOnionPayloadOptional FeatureBit = 9
|
||||
|
||||
// StaticRemoteKeyRequired is a required feature bit that signals that
|
||||
// within one's commitment transaction, the key used for the remote
|
||||
// party's non-delay output should not be tweaked.
|
||||
StaticRemoteKeyRequired FeatureBit = 10
|
||||
|
||||
// StaticRemoteKeyOptional is an optional feature bit that signals that
|
||||
// within one's commitment transaction, the key used for the remote
|
||||
// party's non-delay output should not be tweaked.
|
||||
StaticRemoteKeyOptional FeatureBit = 11
|
||||
|
||||
// maxAllowedSize is a maximum allowed size of feature vector.
|
||||
//
|
||||
// NOTE: Within the protocol, the maximum allowed message size is 65535
|
||||
@ -88,6 +98,8 @@ var LocalFeatures = map[FeatureBit]string{
|
||||
var GlobalFeatures = map[FeatureBit]string{
|
||||
TLVOnionPayloadRequired: "tlv-onion",
|
||||
TLVOnionPayloadOptional: "tlv-onion",
|
||||
StaticRemoteKeyOptional: "static-remote-key",
|
||||
StaticRemoteKeyRequired: "static-remote-key",
|
||||
}
|
||||
|
||||
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
|
||||
|
18
peer.go
18
peer.go
@ -2410,6 +2410,24 @@ func (p *peer) handleInitMsg(msg *lnwire.Init) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalGlobalFeatures returns the set of global features that has been
|
||||
// advertised by the local node. This allows sub-systems that use this
|
||||
// interface to gate their behavior off the set of negotiated feature bits.
|
||||
//
|
||||
// NOTE: Part of the lnpeer.Peer interface.
|
||||
func (p *peer) LocalGlobalFeatures() *lnwire.FeatureVector {
|
||||
return p.server.globalFeatures
|
||||
}
|
||||
|
||||
// RemoteGlobalFeatures returns the set of global features that has been
|
||||
// advertised by the remote node. This allows sub-systems that use this
|
||||
// interface to gate their behavior off the set of negotiated feature bits.
|
||||
//
|
||||
// NOTE: Part of the lnpeer.Peer interface.
|
||||
func (p *peer) RemoteGlobalFeatures() *lnwire.FeatureVector {
|
||||
return p.remoteGlobalFeatures
|
||||
}
|
||||
|
||||
// sendInitMsg sends init message to remote peer which contains our currently
|
||||
// supported local and global features.
|
||||
func (p *peer) sendInitMsg() error {
|
||||
|
@ -2684,6 +2684,7 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
|
||||
ChanStatusFlags: dbChannel.ChanStatus().String(),
|
||||
LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve),
|
||||
RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve),
|
||||
StaticRemoteKey: dbChannel.ChanType.IsTweakless(),
|
||||
}
|
||||
|
||||
for i, htlc := range localCommit.Htlcs {
|
||||
|
11
server.go
11
server.go
@ -324,6 +324,12 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
globalFeatures.Set(lnwire.TLVOnionPayloadOptional)
|
||||
}
|
||||
|
||||
// Similarly, we default to the new modern commitment format unless the
|
||||
// legacy commitment config is set to true.
|
||||
if !cfg.LegacyProtocol.LegacyCommitment() {
|
||||
globalFeatures.Set(lnwire.StaticRemoteKeyOptional)
|
||||
}
|
||||
|
||||
var serializedPubKey [33]byte
|
||||
copy(serializedPubKey[:], privKey.PubKey().SerializeCompressed())
|
||||
|
||||
@ -388,8 +394,9 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
peerConnectedListeners: make(map[string][]chan<- lnpeer.Peer),
|
||||
peerDisconnectedListeners: make(map[string][]chan<- struct{}),
|
||||
|
||||
globalFeatures: lnwire.NewFeatureVector(globalFeatures,
|
||||
lnwire.GlobalFeatures),
|
||||
globalFeatures: lnwire.NewFeatureVector(
|
||||
globalFeatures, lnwire.GlobalFeatures,
|
||||
),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
|
@ -259,6 +259,8 @@ func getInputWitnessSizeUpperBound(inp input.Input) (int, bool, error) {
|
||||
switch inp.WitnessType() {
|
||||
|
||||
// Outputs on a remote commitment transaction that pay directly to us.
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.WitnessKeyHash:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
|
@ -186,9 +186,10 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||
}
|
||||
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
||||
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(channelBal,
|
||||
channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint,
|
||||
*fundingTxIn)
|
||||
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
|
||||
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
||||
bobCommitPoint, *fundingTxIn, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
@ -254,7 +255,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||
IdentityPub: aliceKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ShortChannelID: shortChanID,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: true,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: bobCommitPoint,
|
||||
@ -271,7 +272,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
|
||||
RemoteChanCfg: aliceCfg,
|
||||
IdentityPub: bobKeyPub,
|
||||
FundingOutpoint: *prevOut,
|
||||
ChanType: channeldb.SingleFunder,
|
||||
ChanType: channeldb.SingleFunderTweakless,
|
||||
IsInitiator: false,
|
||||
Capacity: channelCapacity,
|
||||
RemoteCurrentRevocation: aliceCommitPoint,
|
||||
|
@ -254,7 +254,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
||||
// DER-encoded signature under the to-remote pubkey. The sighash flag is
|
||||
// also present, so we trim it.
|
||||
toRemoteWitness, err := input.CommitSpendNoDelay(
|
||||
signer, toRemoteSignDesc, justiceTxn,
|
||||
signer, toRemoteSignDesc, justiceTxn, false,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign to-remote input: %v", err)
|
||||
|
@ -54,7 +54,7 @@ type backupTask struct {
|
||||
// variables.
|
||||
func newBackupTask(chanID *lnwire.ChannelID,
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
sweepPkScript []byte) *backupTask {
|
||||
sweepPkScript []byte, isTweakless bool) *backupTask {
|
||||
|
||||
// Parse the non-dust outputs from the breach transaction,
|
||||
// simultaneously computing the total amount contained in the inputs
|
||||
@ -85,12 +85,18 @@ func newBackupTask(chanID *lnwire.ChannelID,
|
||||
totalAmt += breachInfo.RemoteOutputSignDesc.Output.Value
|
||||
}
|
||||
if breachInfo.LocalOutputSignDesc != nil {
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if isTweakless {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
|
||||
totalAmt += breachInfo.LocalOutputSignDesc.Output.Value
|
||||
}
|
||||
|
||||
@ -271,6 +277,8 @@ func (t *backupTask) craftSessionPayload(
|
||||
case input.CommitmentRevoke:
|
||||
copy(justiceKit.CommitToLocalSig[:], signature[:])
|
||||
|
||||
case input.CommitSpendNoDelayTweakless:
|
||||
fallthrough
|
||||
case input.CommitmentNoDelay:
|
||||
copy(justiceKit.CommitToRemoteSig[:], signature[:])
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ type backupTaskTest struct {
|
||||
bindErr error
|
||||
expSweepScript []byte
|
||||
signer input.Signer
|
||||
tweakless bool
|
||||
}
|
||||
|
||||
// genTaskTest creates a instance of a backupTaskTest using the passed
|
||||
@ -89,7 +90,8 @@ func genTaskTest(
|
||||
rewardScript []byte,
|
||||
expSweepAmt int64,
|
||||
expRewardAmt int64,
|
||||
bindErr error) backupTaskTest {
|
||||
bindErr error,
|
||||
tweakless bool) backupTaskTest {
|
||||
|
||||
// Parse the key pairs for all keys used in the test.
|
||||
revSK, revPK := btcec.PrivKeyFromBytes(
|
||||
@ -188,9 +190,15 @@ func genTaskTest(
|
||||
Hash: txid,
|
||||
Index: index,
|
||||
}
|
||||
|
||||
witnessType := input.CommitmentNoDelay
|
||||
if tweakless {
|
||||
witnessType = input.CommitSpendNoDelayTweakless
|
||||
}
|
||||
|
||||
toRemoteInput = input.NewBaseInput(
|
||||
&breachInfo.LocalOutpoint,
|
||||
input.CommitmentNoDelay,
|
||||
witnessType,
|
||||
breachInfo.LocalOutputSignDesc,
|
||||
0,
|
||||
)
|
||||
@ -218,6 +226,7 @@ func genTaskTest(
|
||||
bindErr: bindErr,
|
||||
expSweepScript: makeAddrSlice(22),
|
||||
signer: signer,
|
||||
tweakless: tweakless,
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,153 +242,6 @@ var (
|
||||
addrScript, _ = txscript.PayToAddrScript(addr)
|
||||
)
|
||||
|
||||
var 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
|
||||
),
|
||||
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
|
||||
),
|
||||
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
|
||||
),
|
||||
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
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate exceeds inputs",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate of 0 creates dust",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
0, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
296117, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
),
|
||||
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
|
||||
),
|
||||
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
|
||||
),
|
||||
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
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate exceeds inputs",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate of 0 creates dust",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
0, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
),
|
||||
}
|
||||
|
||||
// TestBackupTaskBind tests the initialization and binding of a backupTask to a
|
||||
// ClientSession. After a successful bind, all parameters of the justice
|
||||
// transaction should be solidified, so we assert there correctness. In an
|
||||
@ -390,8 +252,174 @@ var backupTaskTests = []backupTaskTest{
|
||||
func TestBackupTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var backupTaskTests []backupTaskTest
|
||||
for _, tweakless := range []bool{true, false} {
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate exceeds inputs",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit no-reward, no outputs, fee rate of 0 creates dust",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitNoReward, // blobType
|
||||
0, // sweepFeeRate
|
||||
nil, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, both outputs",
|
||||
100, // stateNum
|
||||
200000, // toLocalAmt
|
||||
100000, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
296117, // expSweepAmt
|
||||
3000, // expRewardAmt
|
||||
nil, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate exceeds inputs",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
1000, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrFeeExceedsInputs, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
genTaskTest(
|
||||
"commit reward, no outputs, fee rate of 0 creates dust",
|
||||
300, // stateNum
|
||||
0, // toLocalAmt
|
||||
0, // toRemoteAmt
|
||||
blobTypeCommitReward, // blobType
|
||||
0, // sweepFeeRate
|
||||
addrScript, // rewardScript
|
||||
0, // expSweepAmt
|
||||
0, // expRewardAmt
|
||||
wtpolicy.ErrCreatesDust, // bindErr
|
||||
tweakless,
|
||||
),
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, test := range backupTaskTests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testBackupTask(t, test)
|
||||
})
|
||||
}
|
||||
@ -399,7 +427,10 @@ func TestBackupTask(t *testing.T) {
|
||||
|
||||
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)
|
||||
task := newBackupTask(
|
||||
&test.chanID, test.breachInfo, test.expSweepScript,
|
||||
test.tweakless,
|
||||
)
|
||||
|
||||
// Assert that all parameters set during initialization are properly
|
||||
// populated.
|
||||
|
@ -90,8 +90,10 @@ type Client interface {
|
||||
// state. If the method returns nil, the backup is guaranteed to be
|
||||
// successful unless the client is force quit, or the justice
|
||||
// transaction would create dust outputs when trying to abide by the
|
||||
// negotiated policy.
|
||||
BackupState(*lnwire.ChannelID, *lnwallet.BreachRetribution) error
|
||||
// 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
|
||||
|
||||
// Start initializes the watchtower client, allowing it process requests
|
||||
// to backup revoked channel states.
|
||||
@ -564,7 +566,7 @@ 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) error {
|
||||
breachInfo *lnwallet.BreachRetribution, isTweakless bool) error {
|
||||
|
||||
// Retrieve the cached sweep pkscript used for this channel.
|
||||
c.backupMu.Lock()
|
||||
@ -589,7 +591,9 @@ func (c *TowerClient) BackupState(chanID *lnwire.ChannelID,
|
||||
c.chanCommitHeights[*chanID] = breachInfo.RevokedStateNum
|
||||
c.backupMu.Unlock()
|
||||
|
||||
task := newBackupTask(chanID, breachInfo, summary.SweepPkScript)
|
||||
task := newBackupTask(
|
||||
chanID, breachInfo, summary.SweepPkScript, isTweakless,
|
||||
)
|
||||
|
||||
return c.pipeline.QueueBackupTask(task)
|
||||
}
|
||||
|
@ -628,7 +628,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)
|
||||
err := h.client.BackupState(&chanID, retribution, false)
|
||||
if err != expErr {
|
||||
h.t.Fatalf("back error mismatch, want: %v, got: %v",
|
||||
expErr, err)
|
||||
|
Loading…
Reference in New Issue
Block a user