Skip to content

Commit 0700523

Browse files
committed
tapchannel: don't break backward compatibility
This partially reverts commit 3ed142f to restore backward compatibility with nodes running on v0.5.x. The intention with removing this explicit split root assignment was to simplify the code. But it actually would lead to force closes between nodes on v0.6.0 and v0.5.x, because they wouldn't be able to agree on what output should be chosen for the split root (since the behavior was changed).
1 parent 97819b6 commit 0700523

File tree

3 files changed

+149
-4
lines changed

3 files changed

+149
-4
lines changed

tapchannel/aux_closer.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func NewAuxChanCloser(cfg AuxChanCloserCfg) *AuxChanCloser {
104104
// createCloseAlloc is a helper function that creates an allocation for an asset
105105
// close. This does not set a script key, as the script key will be set for each
106106
// packet after the coins have been distributed.
107-
func createCloseAlloc(isLocal bool, outputSum uint64,
107+
func createCloseAlloc(isLocal, isInitiator bool, outputSum uint64,
108108
shutdownMsg tapchannelmsg.AuxShutdownMsg) (*tapsend.Allocation, error) {
109109

110110
// The sort pkScript for the allocation will just be the internal key,
@@ -146,6 +146,7 @@ func createCloseAlloc(isLocal bool, outputSum uint64,
146146

147147
return tapsend.CommitAllocationToRemote
148148
}(),
149+
SplitRoot: isInitiator,
149150
InternalKey: shutdownMsg.AssetInternalKey.Val,
150151
GenScriptKey: scriptKeyGen,
151152
Amount: outputSum,
@@ -306,7 +307,7 @@ func (a *AuxChanCloser) AuxCloseOutputs(
306307
remoteSum := fn.Reduce(commitState.RemoteAssets.Val.Outputs, sumAmounts)
307308
if localSum > 0 {
308309
localAlloc, err = createCloseAlloc(
309-
true, localSum, localShutdown,
310+
true, desc.Initiator, localSum, localShutdown,
310311
)
311312
if err != nil {
312313
return none, err
@@ -318,7 +319,7 @@ func (a *AuxChanCloser) AuxCloseOutputs(
318319
}
319320
if remoteSum > 0 {
320321
remoteAlloc, err = createCloseAlloc(
321-
false, remoteSum, remoteShutdown,
322+
false, !desc.Initiator, remoteSum, remoteShutdown,
322323
)
323324
if err != nil {
324325
return none, err

tapchannel/commitment.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,10 @@ func CreateAllocations(chanState lnwallet.AuxChanState, ourBalance,
714714
initiatorAssetBalance)
715715

716716
// Next, we add the HTLC outputs, using this helper function to
717-
// distinguish between incoming and outgoing HTLCs.
717+
// distinguish between incoming and outgoing HTLCs. The haveHtlcSplit
718+
// boolean is used to store if one of the HTLCs has already been chosen
719+
// to be the split root (only the very first HTLC might be chosen).
720+
var haveHtlcSplitRoot bool
718721
addHtlc := func(htlc *DecodedDescriptor, isIncoming bool) error {
719722
htlcScript, err := lnwallet.GenTaprootHtlcScript(
720723
isIncoming, whoseCommit, htlc.Timeout, htlc.RHash,
@@ -732,6 +735,21 @@ func CreateAllocations(chanState lnwallet.AuxChanState, ourBalance,
732735
"sibling: %w", err)
733736
}
734737

738+
// We should always just have a single split root, which
739+
// normally is the initiator's balance. However, if the
740+
// initiator has no balance, then we choose the very first HTLC
741+
// in the list to be the split root. If there are no HTLCs, then
742+
// all the balance is on the receiver side and we don't need a
743+
// split root.
744+
shouldHouseSplitRoot := initiatorAssetBalance == 0 &&
745+
!haveHtlcSplitRoot
746+
747+
// Make sure we only select the very first HTLC that pays to the
748+
// initiator.
749+
if shouldHouseSplitRoot {
750+
haveHtlcSplitRoot = true
751+
}
752+
735753
allocType := tapsend.CommitAllocationHtlcOutgoing
736754
if isIncoming {
737755
allocType = tapsend.CommitAllocationHtlcIncoming
@@ -779,6 +797,7 @@ func CreateAllocations(chanState lnwallet.AuxChanState, ourBalance,
779797
Type: allocType,
780798
Amount: rfqmsg.Sum(htlc.AssetBalances),
781799
AssetVersion: asset.V1,
800+
SplitRoot: shouldHouseSplitRoot,
782801
BtcAmount: htlc.Amount.ToSatoshis(),
783802
InternalKey: htlcTree.InternalKey,
784803
NonAssetLeaves: sibling,
@@ -982,6 +1001,7 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg,
9821001
Type: tapsend.CommitAllocationToLocal,
9831002
Amount: ourAssetBalance,
9841003
AssetVersion: asset.V1,
1004+
SplitRoot: initiator,
9851005
BtcAmount: ourBalance,
9861006
InternalKey: toLocalTree.InternalKey,
9871007
NonAssetLeaves: sibling,
@@ -1044,6 +1064,7 @@ func addCommitmentOutputs(chanType channeldb.ChannelType, localChanCfg,
10441064
Type: tapsend.CommitAllocationToRemote,
10451065
Amount: theirAssetBalance,
10461066
AssetVersion: asset.V1,
1067+
SplitRoot: !initiator,
10471068
BtcAmount: theirBalance,
10481069
InternalKey: toRemoteTree.InternalKey,
10491070
NonAssetLeaves: sibling,

tapsend/allocation_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,129 @@ func TestDistributeCoins(t *testing.T) {
877877
},
878878
},
879879
},
880+
{
881+
// This tests the backward compatibility case where the
882+
// split root output is defined explicitly, even for an
883+
// interactive packet (which is the case for channels).
884+
name: "single asset, distributed to three outputs",
885+
inputs: []*proof.Proof{
886+
makeProof(t, assetID1Tranche1),
887+
},
888+
interactive: true,
889+
//nolint:lll
890+
allocations: []*Allocation{
891+
{
892+
Type: CommitAllocationHtlcOutgoing,
893+
Amount: 50,
894+
OutputIndex: 2,
895+
},
896+
{
897+
Type: CommitAllocationToLocal,
898+
Amount: 20,
899+
OutputIndex: 3,
900+
},
901+
{
902+
Type: CommitAllocationToRemote,
903+
Amount: 30,
904+
OutputIndex: 4,
905+
SplitRoot: true,
906+
},
907+
},
908+
expectedInputs: map[asset.ID][]asset.ScriptKey{
909+
assetID1.ID(): {
910+
assetID1Tranche1.ScriptKey,
911+
},
912+
},
913+
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
914+
assetID1.ID(): {
915+
{
916+
Amount: 50,
917+
Type: simple,
918+
Interactive: true,
919+
AnchorOutputIndex: 2,
920+
},
921+
{
922+
Amount: 20,
923+
Type: simple,
924+
Interactive: true,
925+
AnchorOutputIndex: 3,
926+
},
927+
{
928+
Amount: 30,
929+
Type: split,
930+
Interactive: true,
931+
AnchorOutputIndex: 4,
932+
},
933+
},
934+
},
935+
},
936+
{
937+
// This shows that for channels with multiple asset IDs,
938+
// defining the split root output might not be enough,
939+
// if some assets aren't even distributed to that
940+
// output. So the fallback for a packet that doesn't
941+
// get an exact split root output is to just use the
942+
// first output as the split root.
943+
name: "multiple allocations, split root defined on " +
944+
"output that gets full value",
945+
inputs: []*proof.Proof{
946+
makeProof(t, assetID4Tranche1),
947+
makeProof(t, assetID5Tranche1),
948+
},
949+
interactive: true,
950+
//nolint:lll
951+
allocations: []*Allocation{
952+
{
953+
Type: CommitAllocationHtlcOutgoing,
954+
Amount: 5000,
955+
OutputIndex: 2,
956+
},
957+
{
958+
Type: CommitAllocationToLocal,
959+
Amount: 20000,
960+
OutputIndex: 3,
961+
},
962+
{
963+
Type: CommitAllocationToRemote,
964+
Amount: 25000,
965+
OutputIndex: 4,
966+
SplitRoot: true,
967+
},
968+
},
969+
vPktVersion: tappsbt.V1,
970+
expectedInputs: map[asset.ID][]asset.ScriptKey{
971+
assetID4.ID(): {
972+
assetID4Tranche1.ScriptKey,
973+
},
974+
assetID5.ID(): {
975+
assetID5Tranche1.ScriptKey,
976+
},
977+
},
978+
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
979+
assetID4.ID(): {
980+
{
981+
Amount: 5000,
982+
Type: split,
983+
Interactive: true,
984+
AnchorOutputIndex: 2,
985+
},
986+
{
987+
Amount: 20000,
988+
Type: simple,
989+
Interactive: true,
990+
AnchorOutputIndex: 3,
991+
},
992+
},
993+
assetID5.ID(): {
994+
{
995+
Amount: 25000,
996+
Type: simple,
997+
Interactive: true,
998+
AnchorOutputIndex: 4,
999+
},
1000+
},
1001+
},
1002+
},
8801003
}
8811004

8821005
for _, tc := range testCases {

0 commit comments

Comments
 (0)