Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 additions & 0 deletions tapchannel/aux_funding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,36 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding,
localAssets = chanAssets
}

// Compute the initial BTC balances so the funding-time allocations
// sort into the same output order as the real commitment transaction
// produced at force-close. If these are left at zero, the asset proofs
// can anchor at the wrong index and fail re-anchor at force close.
commitFee := pendingFunding.feeRate.FeeForWeight(
lnwallet.CommitWeight(lndOpenChan.ChanType),
)
commitReserve := commitFee
if lndOpenChan.ChanType.HasAnchors() {
commitReserve += 2 * lnwallet.AnchorSize
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the totalFee here represents total reserved sats not just miner fees. the calculation is good, is there a more descriptive name for it?


capacityMSat := lnwire.NewMSatFromSatoshis(lndOpenChan.Capacity)
pushMSat := lnwire.NewMSatFromSatoshis(pendingFunding.pushAmt)
reserveMSat := lnwire.NewMSatFromSatoshis(commitReserve)
if capacityMSat < reserveMSat+pushMSat {
return nil, lnwallet.CommitAuxLeaves{}, fmt.Errorf("invalid "+
"initial balances: capacity=%v push=%v reserve=%v",
lndOpenChan.Capacity, pendingFunding.pushAmt,
commitReserve)
}

var localSatBalance, remoteSatBalance lnwire.MilliSatoshi
if pendingFunding.initiator {
localSatBalance = capacityMSat - reserveMSat - pushMSat
remoteSatBalance = pushMSat
} else {
localSatBalance = pushMSat
remoteSatBalance = capacityMSat - reserveMSat - pushMSat
}
Comment thread
GeorgeTsagk marked this conversation as resolved.

// We don't have a real prev state at this point, the leaf creator only
// needs the sum of the remote+local assets, so we'll populate that.
Expand Down
144 changes: 97 additions & 47 deletions tapchannel/aux_sweeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1173,71 +1173,116 @@ func reanchorAssetOutputs(ctx context.Context,
return nil
}

// anchorOutputAllocations is a helper function that creates a set of
// allocations for the anchor outputs. We'll use this later to create the proper
// exclusion proofs.
func anchorOutputAllocations(
keyRing *lnwallet.CommitmentKeyRing) lfn.Result[[]*tapsend.Allocation] {
// commitOutputAllocations builds NoAssets allocations for the actual pure-BTC
// outputs of the real commitment transaction. This lets force-close exclusion
// proofs match the transaction that is on chain instead of assuming the first
// two outputs are always anchors.
func commitOutputAllocations(req lnwallet.ResolutionReq,
vPackets []*tappsbt.VPacket) lfn.Result[[]*tapsend.Allocation] {

anchorAlloc := func(
k *btcec.PublicKey) lfn.Result[*tapsend.Allocation] {
if req.CommitTx == nil {
return lfn.Err[[]*tapsend.Allocation](
fmt.Errorf("commit tx not set"),
)
}

assetOutputs := make(map[uint32]struct{})
for _, vPkt := range vPackets {
for _, vOut := range vPkt.Outputs {
assetOutputs[vOut.AnchorOutputIndex] = struct{}{}
}
}

findOutputIndex := func(pkScript []byte) (uint32, bool) {
for idx, txOut := range req.CommitTx.TxOut {
if bytes.Equal(txOut.PkScript, pkScript) {
return uint32(idx), true
}
}

return 0, false
}

anchorTree, err := input.NewAnchorScriptTree(k)
newNoAssetAlloc := func(desc input.ScriptDescriptor) (
*tapsend.Allocation, error) {

sibling, scriptTree, err := LeavesFromTapscriptScriptTree(desc)
if err != nil {
return lfn.Err[*tapsend.Allocation](err)
return nil, err
}

sibling, scriptTree, err := LeavesFromTapscriptScriptTree(
anchorTree,
pkScript, err := txscript.PayToTaprootScript(
scriptTree.TaprootKey,
)
if err != nil {
return lfn.Err[*tapsend.Allocation](err)
return nil, err
}

outputIndex, ok := findOutputIndex(pkScript)
if !ok {
return nil, nil
}
if _, hasAssets := assetOutputs[outputIndex]; hasAssets {
return nil, nil
}

return lfn.Ok(&tapsend.Allocation{
Type: tapsend.AllocationTypeNoAssets,
Amount: 0,
BtcAmount: lnwallet.AnchorSize,
return &tapsend.Allocation{
Type: tapsend.AllocationTypeNoAssets,
OutputIndex: outputIndex,
BtcAmount: btcutil.Amount(
req.CommitTx.TxOut[outputIndex].Value,
),
InternalKey: scriptTree.InternalKey,
NonAssetLeaves: sibling,
SortTaprootKeyBytes: schnorr.SerializePubKey(
scriptTree.TaprootKey,
),
})
}, nil
}

localAnchor := anchorAlloc(keyRing.ToLocalKey)
remoteAnchor := anchorAlloc(keyRing.ToRemoteKey)

type resultType = lfn.Result[[]*tapsend.Allocation]
sortAnchor := func(a1, a2 *tapsend.Allocation) resultType {
// Before we return the anchors, we'll make sure that
// they end up in the right sort order.
scriptCompare := bytes.Compare(
a1.SortTaprootKeyBytes, a2.SortTaprootKeyBytes,
)
toLocalTree, err := input.NewLocalCommitScriptTree(
req.CsvDelay, req.KeyRing.ToLocalKey, req.KeyRing.RevocationKey,
input.NoneTapLeaf(),
)
if err != nil {
return lfn.Err[[]*tapsend.Allocation](err)
}

if scriptCompare < 0 {
a1.OutputIndex = 0
a2.OutputIndex = 1
} else {
a2.OutputIndex = 0
a1.OutputIndex = 1
}
toRemoteTree, err := input.NewRemoteCommitScriptTree(
req.KeyRing.ToRemoteKey, input.NoneTapLeaf(),
)
if err != nil {
return lfn.Err[[]*tapsend.Allocation](err)
}

return lfn.Ok([]*tapsend.Allocation{a1, a2})
localAnchorTree, err := input.NewAnchorScriptTree(
req.KeyRing.ToLocalKey,
)
if err != nil {
return lfn.Err[[]*tapsend.Allocation](err)
}

return lfn.FlatMapResult(
localAnchor, func(a1 *tapsend.Allocation) resultType {
return lfn.FlatMapResult(
remoteAnchor,
func(a2 *tapsend.Allocation) resultType {
return sortAnchor(a1, a2)
},
)
},
remoteAnchorTree, err := input.NewAnchorScriptTree(
req.KeyRing.ToRemoteKey,
)
if err != nil {
return lfn.Err[[]*tapsend.Allocation](err)
}

allocations := make([]*tapsend.Allocation, 0, 4)
for _, desc := range []input.ScriptDescriptor{
toLocalTree, toRemoteTree, localAnchorTree, remoteAnchorTree,
} {
alloc, err := newNoAssetAlloc(desc)
if err != nil {
return lfn.Err[[]*tapsend.Allocation](err)
}
if alloc != nil {
allocations = append(allocations, alloc)
}
}

return lfn.Ok(allocations)
}

// remoteCommitScriptKey creates the script key for the remote commitment
Expand Down Expand Up @@ -1799,9 +1844,14 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq,
"commitments: %w", err)
}

// With the output commitments known, we can regenerate the proof suffix
// for each vPkt.
anchorAllocations, err := anchorOutputAllocations(req.KeyRing).Unpack()
// With the output commitments known, we can regenerate the proof
// suffix for each vPkt. Recompute the non-asset allocations from the
// real commit tx outputs so the exclusion proofs line up with what's
// actually on chain, regardless of whether this is a symmetric or an
// asymmetric commitment.
anchorAllocations, err := commitOutputAllocations(
req, vPackets,
).Unpack()
if err != nil {
return fmt.Errorf("unable to create anchor "+
"allocations: %w", err)
Expand Down
Loading