Skip to content

Commit c3e15eb

Browse files
authored
Merge pull request #1514 from lightninglabs/burn-flake-fix
itest flake: fixes flake in burn test
2 parents bb5a2a6 + 3174374 commit c3e15eb

File tree

4 files changed

+196
-40
lines changed

4 files changed

+196
-40
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ flake-unit-trace:
230230

231231
flake-unit-race:
232232
@$(call print, "Flake hunting races in unit tests.")
233-
while [ $$? -eq 0 ]; do $(GOLIST) | $(XARGS) env CGO_ENABLED=1 GORACE="history_size=7 halt_on_errors=1" $(GOTEST) -race -test.timeout=20m -count=1; done
233+
while [ $$? -eq 0 ]; do make unit-race nocache=1; done
234234

235235
# =============
236236
# FUZZING

tapfreighter/wallet.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"slices"
89
"time"
910

1011
"github.com/btcsuite/btcd/blockchain"
@@ -525,7 +526,13 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
525526
}
526527

527528
// Now that we know what inputs we're going to spend, we know that by
528-
// definition, we use the first input's info as the burn's PrevID.
529+
// definition, we use the first input's info as the burn's PrevID. But
530+
// to know which input will actually be assigned as the first input in
531+
// the allocated virtual packet, we first apply the same sorting that
532+
// the allocation code will also apply.
533+
slices.SortFunc(activeAssets, func(a, b *AnchoredCommitment) int {
534+
return tapsend.AssetSortForInputs(*a.Asset, *b.Asset)
535+
})
529536
firstInput := activeAssets[0]
530537
firstPrevID := asset.PrevID{
531538
OutPoint: firstInput.AnchorPoint,

tapsend/allocation.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package tapsend
22

33
import (
44
"bytes"
5+
"cmp"
56
"errors"
67
"fmt"
78
"net/url"
9+
"slices"
810
"sort"
911

1012
"github.com/btcsuite/btcd/btcec/v2"
@@ -389,27 +391,30 @@ func sortPiecesWithProofs(pieces []*piece) {
389391
// Now sort all the proofs within each piece by amount and then script
390392
// key. This will give us a stable order for all asset UTXOs.
391393
for idx := range pieces {
392-
sort.Slice(pieces[idx].proofs, func(i, j int) bool {
393-
assetI := pieces[idx].proofs[i].Asset
394-
assetJ := pieces[idx].proofs[j].Asset
395-
396-
// If amounts are equal, sort by script key.
397-
if assetI.Amount == assetJ.Amount {
398-
keyI := assetI.ScriptKey.PubKey
399-
keyJ := assetJ.ScriptKey.PubKey
400-
return bytes.Compare(
401-
keyI.SerializeCompressed(),
402-
keyJ.SerializeCompressed(),
403-
) < 0
404-
}
405-
406-
// Otherwise, sort by amount, but in reverse order so
407-
// that the largest amounts are first.
408-
return assetI.Amount > assetJ.Amount
409-
})
394+
slices.SortFunc(
395+
pieces[idx].proofs, func(i, j *proof.Proof) int {
396+
return AssetSortForInputs(i.Asset, j.Asset)
397+
},
398+
)
410399
}
411400
}
412401

402+
// AssetSortForInputs is a comparison function that should be used to sort asset
403+
// inputs by amount (in reverse order) and then by script key. Using this
404+
// function everywhere we sort inputs will ensure that the inputs are always in
405+
// a predictable and stable order.
406+
func AssetSortForInputs(i, j asset.Asset) int {
407+
return cmp.Or(
408+
// Sort amounts in reverse order so that the largest amounts are
409+
// first.
410+
cmp.Compare(j.Amount, i.Amount),
411+
bytes.Compare(
412+
i.ScriptKey.PubKey.SerializeCompressed(),
413+
j.ScriptKey.PubKey.SerializeCompressed(),
414+
),
415+
)
416+
}
417+
413418
// DistributeCoins allocates a set of inputs (extracted from the given input
414419
// proofs) to virtual outputs as specified by the allocations given. It returns
415420
// a list of virtual packets (one for each distinct asset ID) with virtual
@@ -499,6 +504,14 @@ func DistributeCoins(inputs []*proof.Proof, allocations []*Allocation,
499504
},
500505
)
501506

507+
// Before creating the virtual packet, sort the proofs by
508+
// amount (in reverse order) then by script key. This ensures
509+
// deterministic ordering before assigning them to the virtual
510+
// packet inputs.
511+
slices.SortFunc(proofsByID, func(i, j *proof.Proof) int {
512+
return AssetSortForInputs(i.Asset, j.Asset)
513+
})
514+
502515
pkt, err := tappsbt.FromProofs(
503516
proofsByID, chainParams, vPktVersion,
504517
)

tapsend/allocation_test.go

Lines changed: 156 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ func TestDistributeCoins(t *testing.T) {
410410
vPktVersion: tappsbt.V1,
411411
expectedInputs: map[asset.ID][]asset.ScriptKey{
412412
assetID2.ID(): {
413-
assetID2Tranche1.ScriptKey,
414413
assetID2Tranche2.ScriptKey,
414+
assetID2Tranche1.ScriptKey,
415415
},
416416
},
417417
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
@@ -452,8 +452,8 @@ func TestDistributeCoins(t *testing.T) {
452452
},
453453
expectedInputs: map[asset.ID][]asset.ScriptKey{
454454
assetID2.ID(): {
455-
assetID2Tranche1.ScriptKey,
456455
assetID2Tranche2.ScriptKey,
456+
assetID2Tranche1.ScriptKey,
457457
},
458458
},
459459
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
@@ -613,19 +613,19 @@ func TestDistributeCoins(t *testing.T) {
613613
},
614614
expectedInputs: map[asset.ID][]asset.ScriptKey{
615615
assetID1.ID(): {
616-
assetID1Tranche1.ScriptKey,
617-
assetID1Tranche2.ScriptKey,
618616
assetID1Tranche3.ScriptKey,
617+
assetID1Tranche2.ScriptKey,
618+
assetID1Tranche1.ScriptKey,
619619
},
620620
assetID2.ID(): {
621-
assetID2Tranche1.ScriptKey,
622-
assetID2Tranche2.ScriptKey,
623621
assetID2Tranche3.ScriptKey,
622+
assetID2Tranche2.ScriptKey,
623+
assetID2Tranche1.ScriptKey,
624624
},
625625
assetID3.ID(): {
626-
assetID3Tranche1.ScriptKey,
627-
assetID3Tranche2.ScriptKey,
628626
assetID3Tranche3.ScriptKey,
627+
assetID3Tranche2.ScriptKey,
628+
assetID3Tranche1.ScriptKey,
629629
},
630630
},
631631
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
@@ -689,19 +689,19 @@ func TestDistributeCoins(t *testing.T) {
689689
},
690690
expectedInputs: map[asset.ID][]asset.ScriptKey{
691691
assetID1.ID(): {
692-
assetID1Tranche1.ScriptKey,
693-
assetID1Tranche2.ScriptKey,
694692
assetID1Tranche3.ScriptKey,
693+
assetID1Tranche2.ScriptKey,
694+
assetID1Tranche1.ScriptKey,
695695
},
696696
assetID2.ID(): {
697-
assetID2Tranche1.ScriptKey,
698-
assetID2Tranche2.ScriptKey,
699697
assetID2Tranche3.ScriptKey,
698+
assetID2Tranche2.ScriptKey,
699+
assetID2Tranche1.ScriptKey,
700700
},
701701
assetID3.ID(): {
702-
assetID3Tranche1.ScriptKey,
703-
assetID3Tranche2.ScriptKey,
704702
assetID3Tranche3.ScriptKey,
703+
assetID3Tranche2.ScriptKey,
704+
assetID3Tranche1.ScriptKey,
705705
},
706706
},
707707
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
@@ -770,19 +770,19 @@ func TestDistributeCoins(t *testing.T) {
770770
},
771771
expectedInputs: map[asset.ID][]asset.ScriptKey{
772772
assetID1.ID(): {
773-
assetID1Tranche1.ScriptKey,
774-
assetID1Tranche2.ScriptKey,
775773
assetID1Tranche3.ScriptKey,
774+
assetID1Tranche2.ScriptKey,
775+
assetID1Tranche1.ScriptKey,
776776
},
777777
assetID2.ID(): {
778-
assetID2Tranche1.ScriptKey,
779-
assetID2Tranche2.ScriptKey,
780778
assetID2Tranche3.ScriptKey,
779+
assetID2Tranche2.ScriptKey,
780+
assetID2Tranche1.ScriptKey,
781781
},
782782
assetID3.ID(): {
783-
assetID3Tranche1.ScriptKey,
784-
assetID3Tranche2.ScriptKey,
785783
assetID3Tranche3.ScriptKey,
784+
assetID3Tranche2.ScriptKey,
785+
assetID3Tranche1.ScriptKey,
786786
},
787787
},
788788
expectedOutputs: map[asset.ID][]*tappsbt.VOutput{
@@ -1230,3 +1230,139 @@ func TestAllocationsFromTemplate(t *testing.T) {
12301230
})
12311231
}
12321232
}
1233+
1234+
// TestSortPiecesWithProofs tests that we can sort pieces of assets with their
1235+
// proofs. The sorting is done first by asset ID, then by the amount of the
1236+
// proofs in descending order and then by script key.
1237+
func TestSortPiecesWithProofs(t *testing.T) {
1238+
key1 := asset.NewScriptKey(test.ParsePubKey(
1239+
t, "03a15fd6e1fded33270ae01183dfc8f8edd1274644b7d014ac5ab576f"+
1240+
"bf8328b05",
1241+
))
1242+
key2 := asset.NewScriptKey(test.ParsePubKey(
1243+
t, "029191ec924fb3c6bbd0d264d0b3cf97fcb2fc1eb5737184e7e17e35c"+
1244+
"6609ee853",
1245+
))
1246+
tests := []struct {
1247+
name string
1248+
input []*piece
1249+
expected []*piece
1250+
}{{
1251+
name: "sort by asset ID and proofs by amount",
1252+
input: []*piece{{
1253+
assetID: asset.ID{0x02},
1254+
proofs: []*proof.Proof{{
1255+
Asset: asset.Asset{
1256+
Amount: 50,
1257+
ScriptKey: key1,
1258+
},
1259+
}, {
1260+
Asset: asset.Asset{
1261+
Amount: 300,
1262+
ScriptKey: key2,
1263+
},
1264+
}, {
1265+
Asset: asset.Asset{
1266+
Amount: 100,
1267+
ScriptKey: key2,
1268+
},
1269+
}},
1270+
}, {
1271+
assetID: asset.ID{0x01},
1272+
proofs: []*proof.Proof{{
1273+
Asset: asset.Asset{
1274+
Amount: 200,
1275+
ScriptKey: key1,
1276+
},
1277+
}, {
1278+
Asset: asset.Asset{
1279+
Amount: 150,
1280+
ScriptKey: key2,
1281+
},
1282+
}},
1283+
}},
1284+
expected: []*piece{{
1285+
assetID: asset.ID{0x01},
1286+
proofs: []*proof.Proof{{
1287+
Asset: asset.Asset{
1288+
Amount: 200,
1289+
ScriptKey: key1,
1290+
},
1291+
}, {
1292+
Asset: asset.Asset{
1293+
Amount: 150,
1294+
ScriptKey: key2,
1295+
},
1296+
}},
1297+
}, {
1298+
assetID: asset.ID{0x02},
1299+
proofs: []*proof.Proof{{
1300+
Asset: asset.Asset{
1301+
Amount: 300,
1302+
ScriptKey: key2,
1303+
},
1304+
}, {
1305+
Asset: asset.Asset{
1306+
Amount: 100,
1307+
ScriptKey: key2,
1308+
},
1309+
}, {
1310+
Asset: asset.Asset{
1311+
Amount: 50,
1312+
ScriptKey: key1,
1313+
},
1314+
}},
1315+
}},
1316+
}, {
1317+
name: "script keys after amount",
1318+
input: []*piece{{
1319+
assetID: asset.ID{0x01},
1320+
proofs: []*proof.Proof{{
1321+
Asset: asset.Asset{
1322+
Amount: 50,
1323+
ScriptKey: key1,
1324+
},
1325+
}, {
1326+
Asset: asset.Asset{
1327+
Amount: 50,
1328+
ScriptKey: key2,
1329+
},
1330+
}, {
1331+
Asset: asset.Asset{
1332+
Amount: 50,
1333+
ScriptKey: key2,
1334+
},
1335+
}},
1336+
}},
1337+
expected: []*piece{{
1338+
assetID: asset.ID{0x01},
1339+
proofs: []*proof.Proof{{
1340+
Asset: asset.Asset{
1341+
Amount: 50,
1342+
ScriptKey: key2,
1343+
},
1344+
}, {
1345+
Asset: asset.Asset{
1346+
Amount: 50,
1347+
ScriptKey: key2,
1348+
},
1349+
}, {
1350+
Asset: asset.Asset{
1351+
Amount: 50,
1352+
ScriptKey: key1,
1353+
},
1354+
}},
1355+
}},
1356+
}, {
1357+
name: "empty input",
1358+
input: []*piece{},
1359+
expected: []*piece{},
1360+
}}
1361+
1362+
for _, tt := range tests {
1363+
t.Run(tt.name, func(t *testing.T) {
1364+
sortPiecesWithProofs(tt.input)
1365+
require.Equal(t, tt.expected, tt.input)
1366+
})
1367+
}
1368+
}

0 commit comments

Comments
 (0)