Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[anchors] Pluggable anchor commitments #3798

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion breacharbiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/commitmenttx"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
)
Expand Down Expand Up @@ -1797,9 +1798,13 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
}
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])

commitType := commitmenttx.NewCommitmentType(
channeldb.SingleFunderTweaklessBit,
)

aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, true,
bobCommitPoint, *fundingTxIn, commitType,
)
if err != nil {
return nil, nil, nil, err
Expand Down
36 changes: 36 additions & 0 deletions channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ const (
// disk. This bit may be on if the funding transaction was crafted by a
// wallet external to the primary daemon.
NoFundingTxBit ChannelType = 1 << 2

// AnchorOutputsBit indicates that the channel makes use of anchor
// outputs to bump the commitment transaction's effective feerate. This
// channel type also uses a delayed to_remote output script. If bit is
// set, we'll find the size of the anchor outputs in the database.
AnchorOutputsBit ChannelType = 1 << 3
)

// IsSingleFunder returns true if the channel type if one of the known single
Expand All @@ -187,6 +193,12 @@ func (c ChannelType) HasFundingTx() bool {
return c&NoFundingTxBit == 0
}

// HasAnchors returns true if this channel type has anchor ouputs on its
// commitment.
func (c ChannelType) HasAnchors() bool {
return c&AnchorOutputsBit == AnchorOutputsBit
}

// 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
Expand Down Expand Up @@ -311,10 +323,14 @@ type ChannelCommitment struct {

// LocalBalance is the current available settled balance within the
// channel directly spendable by us.
//
// NOTE: This is the balance _after_ subtracting any commitment fee.
LocalBalance lnwire.MilliSatoshi

// RemoteBalance is the current available settled balance within the
// channel directly spendable by the remote node.
//
// NOTE: This is the balance _after_ subtracting any commitment fee.
RemoteBalance lnwire.MilliSatoshi

// CommitFee is the amount calculated to be paid in fees for the
Expand Down Expand Up @@ -497,6 +513,10 @@ type OpenChannel struct {
// Capacity is the total capacity of this channel.
Capacity btcutil.Amount

// AnchorSize is the size of the anchor outputs for this channel. Only
// set if the AnchorOutputsBit is set for the channel type.
AnchorSize btcutil.Amount

// TotalMSatSent is the total number of milli-satoshis we've sent
// within this channel.
TotalMSatSent lnwire.MilliSatoshi
Expand Down Expand Up @@ -2593,6 +2613,14 @@ func putChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
return err
}

// If this is an anchor type, write the anchor size.
if channel.ChanType.HasAnchors() {
err := WriteElement(&w, channel.AnchorSize)
if err != nil {
return err
}
}

if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err
}
Expand Down Expand Up @@ -2767,6 +2795,14 @@ func fetchChanInfo(chanBucket *bbolt.Bucket, channel *OpenChannel) error {
return err
}

// If this channel type has anchors, read the anchor size.
if channel.ChanType.HasAnchors() {
err := ReadElement(r, &channel.AnchorSize)
if err != nil {
return err
}
}

channel.Packager = NewChannelPackager(channel.ShortChannelID)

// Finally, read the optional shutdown scripts.
Expand Down
36 changes: 21 additions & 15 deletions contractcourt/chain_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/commitmenttx"
"github.com/lightningnetwork/lnd/shachain"
)

Expand Down Expand Up @@ -331,7 +332,8 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
// based off of only the set of outputs included.
func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a method on CommitmentType too?

commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
revocationProducer shachain.Producer, tweakless bool) (bool, error) {
revocationProducer shachain.Producer,
commitType commitmenttx.CommitmentType) (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.
Expand All @@ -344,14 +346,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.
commitKeyRing := lnwallet.DeriveCommitmentKeys(
commitPoint, true, tweakless, &localChanCfg, &remoteChanCfg,
commitKeyRing := commitType.DeriveCommitmentKeys(
commitPoint, true, &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(
commitKeyRing.NoDelayKey,
remoteDelay := uint32(remoteChanCfg.CsvDelay)
remoteScript, err := commitType.CommitScriptToRemote(
remoteDelay, commitKeyRing.RemoteKey,
)
if err != nil {
return false, err
Expand All @@ -361,7 +364,7 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
// the remote party allowing them to claim this output before the CSV
// delay if we breach.
localScript, err := input.CommitScriptToSelf(
uint32(localChanCfg.CsvDelay), commitKeyRing.DelayKey,
uint32(localChanCfg.CsvDelay), commitKeyRing.LocalKey,
commitKeyRing.RevocationKey,
)
if err != nil {
Expand All @@ -382,7 +385,7 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
case bytes.Equal(localPkScript, pkScript):
return true, nil

case bytes.Equal(remotePkScript, pkScript):
case bytes.Equal(remoteScript.PkScript, pkScript):
return true, nil
}
}
Expand Down Expand Up @@ -422,11 +425,6 @@ 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 "+
Expand Down Expand Up @@ -477,14 +475,21 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
commitTxBroadcast, obfuscator,
)

// An additional piece of information we need to properly
// dispatch a close event is the commitment format this channel
// was using.
commitType := commitmenttx.NewCommitmentType(
c.cfg.chanState.ChanType,
)

// Based on the output scripts within this commitment, we'll
// determine if this is our commitment transaction or not (a
// self force close).
isOurCommit, err := isOurCommitment(
c.cfg.chanState.LocalChanCfg,
c.cfg.chanState.RemoteChanCfg, commitSpend,
broadcastStateNum, c.cfg.chanState.RevocationProducer,
tweaklessCommit,
commitType,
)
if err != nil {
log.Errorf("unable to determine self commit for "+
Expand Down Expand Up @@ -596,6 +601,7 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
// close and sweep immediately using a fake commitPoint
// as it isn't actually needed for recovery anymore.
commitPoint := c.cfg.chanState.RemoteCurrentRevocation
tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
if !tweaklessCommit {
commitPoint = c.waitForCommitmentPoint()
if commitPoint == nil {
Expand Down Expand Up @@ -928,8 +934,8 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail
retribution.KeyRing.CommitPoint.Curve = nil
retribution.KeyRing.LocalHtlcKey = nil
retribution.KeyRing.RemoteHtlcKey = nil
retribution.KeyRing.DelayKey = nil
retribution.KeyRing.NoDelayKey = nil
retribution.KeyRing.LocalKey = nil
retribution.KeyRing.RemoteKey = nil
retribution.KeyRing.RevocationKey = nil
return spew.Sdump(retribution)
}))
Expand Down
2 changes: 2 additions & 0 deletions fundingmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
Flags: msg.ChannelFlags,
MinConfs: 1,
Tweakless: tweaklessCommitment,
AnchorSize: 0,
}

reservation, err := f.cfg.Wallet.InitChannelReservation(req)
Expand Down Expand Up @@ -2893,6 +2894,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
Flags: channelFlags,
MinConfs: msg.minConfs,
Tweakless: tweaklessCommitment,
AnchorSize: 0,
}

reservation, err := f.cfg.Wallet.InitChannelReservation(req)
Expand Down
9 changes: 7 additions & 2 deletions htlcswitch/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/commitmenttx"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/ticker"
Expand Down Expand Up @@ -260,9 +261,13 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
}
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])

commitType := commitmenttx.NewCommitmentType(
channeldb.SingleFunderTweaklessBit,
)

aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, true,
aliceAmount, bobAmount, 294, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, commitType,
)
if err != nil {
return nil, nil, nil, err
Expand Down
56 changes: 56 additions & 0 deletions input/script_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,62 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
return builder.Script()
}

// CommitScriptAnchor constructs the script for the anchor output spendable by
// the given key immediately, or by anyone after 10 confirmations.
//
// OP_DEPTH
// OP_IF
// <funding_pubkey> OP_CHECKSIG
// OP_ELSE
// 10 OP_CSV
// OP_ENDIF
func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()

// OP_DEPTh to check if this is a spend with a key, or anyone can spend
// clause.
builder.AddOp(txscript.OP_DEPTH)

// If spend with key
builder.AddOp(txscript.OP_IF)
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIG)

// Otherswise it just have to be 10 blocks deep.
builder.AddOp(txscript.OP_ELSE)
builder.AddInt64(10)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)

builder.AddOp(txscript.OP_ENDIF)

return builder.Script()
}

// CommitScriptToRemote constructs the script for the output on the commitment
// transaction paying to the remote party of said commitment transaction. The
// money can only be spend after the timeout has passed.
//
// Possible Input Scripts:
// SWEEP: <sig>
//
// Output Script:
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY OP_DROP <key> OP_CHECKSIG
func CommitScriptToRemote(csvTimeout uint32, key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()

// Check that the CSV delay has passed
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)

// If that's the case, let the output be spent by a signature by the
// given key.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIG)

return builder.Script()
}

// CommitSpendTimeout constructs a valid witness allowing the owner of a
// particular commitment transaction to spend the output returning settled
// funds back to themselves after a relative block timeout. In order to
Expand Down
41 changes: 32 additions & 9 deletions input/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ import (
"github.com/btcsuite/btcd/wire"
)

const (
// CommitWeight is the weight of the base commitment transaction which
// includes: one p2wsh input, out p2wkh output, and one p2wsh output.
CommitWeight int64 = 724

// HtlcWeight is the weight of an HTLC output.
HtlcWeight int64 = 172
)

const (
// witnessScaleFactor determines the level of "discount" witness data
// receives compared to "base" data. A scale factor of 4, denotes that
Expand Down Expand Up @@ -131,6 +122,12 @@ const (
// - PkScript (P2WPKH)
CommitmentKeyHashOutput = 8 + 1 + P2WPKHSize

// CommitmentAnchorOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2WSH)
CommitmentAnchorOutput = 8 + 1 + P2WSHSize

// HTLCSize 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
Expand Down Expand Up @@ -168,6 +165,32 @@ const (
// WitnessCommitmentTxWeight 224 weight
WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize

// BaseAnchorCommitmentTxSize 223 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 1 byte
// - TxOut: 4*43 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// AnchorPayingToThem,
// AnchorPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseAnchorCommitmentTxSize = 4 + 1 + FundingInputSize + 1 +
2*CommitmentDelayOutput + 2*CommitmentAnchorOutput + 4

// BaseAnchorCommitmentTxWeight 892 weight
BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize

// CommitWeight 724 weight
CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight

// AnchorCommitWeight 1116 weight
AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight

// HTLCWeight 172 weight
HTLCWeight = witnessScaleFactor * HTLCSize

Expand Down
Loading