|  | 
| 1 | 1 | package sweepbatcher | 
| 2 | 2 | 
 | 
| 3 | 3 | import ( | 
|  | 4 | +	"bytes" | 
| 4 | 5 | 	"context" | 
| 5 | 6 | 	"fmt" | 
| 6 | 7 | 	"os" | 
| @@ -1268,6 +1269,147 @@ func testPresigned_presigned_group_with_change(t *testing.T, | 
| 1268 | 1269 | 	require.NoError(t, lnd.NotifyHeight(601)) | 
| 1269 | 1270 | } | 
| 1270 | 1271 | 
 | 
|  | 1272 | +// testPresigned_fee_portion_with_change ensures that the fee portion reported | 
|  | 1273 | +// to clients accounts for change outputs in the presigned transaction. | 
|  | 1274 | +func testPresigned_fee_portion_with_change(t *testing.T, | 
|  | 1275 | +	batcherStore testBatcherStore) { | 
|  | 1276 | + | 
|  | 1277 | +	defer test.Guard(t)() | 
|  | 1278 | + | 
|  | 1279 | +	lnd := test.NewMockLnd() | 
|  | 1280 | + | 
|  | 1281 | +	ctx, cancel := context.WithCancel(context.Background()) | 
|  | 1282 | +	defer cancel() | 
|  | 1283 | + | 
|  | 1284 | +	customFeeRate := func(_ context.Context, _ lntypes.Hash, | 
|  | 1285 | +		_ wire.OutPoint) (chainfee.SatPerKWeight, error) { | 
|  | 1286 | + | 
|  | 1287 | +		return chainfee.SatPerKWeight(10_000), nil | 
|  | 1288 | +	} | 
|  | 1289 | + | 
|  | 1290 | +	presignedHelper := newMockPresignedHelper() | 
|  | 1291 | + | 
|  | 1292 | +	batcher := NewBatcher( | 
|  | 1293 | +		lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, | 
|  | 1294 | +		testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, | 
|  | 1295 | +		batcherStore, presignedHelper, | 
|  | 1296 | +		WithCustomFeeRate(customFeeRate), | 
|  | 1297 | +		WithPresignedHelper(presignedHelper), | 
|  | 1298 | +	) | 
|  | 1299 | + | 
|  | 1300 | +	go func() { | 
|  | 1301 | +		err := batcher.Run(ctx) | 
|  | 1302 | +		checkBatcherError(t, err) | 
|  | 1303 | +	}() | 
|  | 1304 | + | 
|  | 1305 | +	swapHash := lntypes.Hash{2, 2, 2} | 
|  | 1306 | +	op := wire.OutPoint{ | 
|  | 1307 | +		Hash:  chainhash.Hash{2, 2}, | 
|  | 1308 | +		Index: 2, | 
|  | 1309 | +	} | 
|  | 1310 | +	group := []Input{ | 
|  | 1311 | +		{ | 
|  | 1312 | +			Outpoint: op, | 
|  | 1313 | +			Value:    1_000_000, | 
|  | 1314 | +		}, | 
|  | 1315 | +	} | 
|  | 1316 | +	change := &wire.TxOut{ | 
|  | 1317 | +		Value:    250_000, | 
|  | 1318 | +		PkScript: []byte{0xca, 0xfe}, | 
|  | 1319 | +	} | 
|  | 1320 | + | 
|  | 1321 | +	presignedHelper.setChangeForPrimaryDeposit(op, change) | 
|  | 1322 | +	presignedHelper.SetOutpointOnline(op, true) | 
|  | 1323 | + | 
|  | 1324 | +	require.NoError(t, batcher.PresignSweepsGroup( | 
|  | 1325 | +		ctx, group, sweepTimeout, destAddr, change, | 
|  | 1326 | +	)) | 
|  | 1327 | + | 
|  | 1328 | +	spendChan := make(chan *SpendDetail, 1) | 
|  | 1329 | +	confChan := make(chan *ConfDetail, 1) | 
|  | 1330 | +	notifier := &SpendNotifier{ | 
|  | 1331 | +		SpendChan:    spendChan, | 
|  | 1332 | +		SpendErrChan: make(chan error, 1), | 
|  | 1333 | +		ConfChan:     confChan, | 
|  | 1334 | +		ConfErrChan:  make(chan error, 1), | 
|  | 1335 | +		QuitChan:     make(chan bool, 1), | 
|  | 1336 | +	} | 
|  | 1337 | + | 
|  | 1338 | +	require.NoError(t, batcher.AddSweep(ctx, &SweepRequest{ | 
|  | 1339 | +		SwapHash: swapHash, | 
|  | 1340 | +		Inputs:   group, | 
|  | 1341 | +		Notifier: notifier, | 
|  | 1342 | +	})) | 
|  | 1343 | + | 
|  | 1344 | +	spendReg := <-lnd.RegisterSpendChannel | 
|  | 1345 | +	require.NotNil(t, spendReg) | 
|  | 1346 | +	require.NotNil(t, spendReg.Outpoint) | 
|  | 1347 | +	require.Equal(t, op, *spendReg.Outpoint) | 
|  | 1348 | + | 
|  | 1349 | +	tx := <-lnd.TxPublishChannel | 
|  | 1350 | +	require.Len(t, tx.TxIn, 1) | 
|  | 1351 | +	require.Len(t, tx.TxOut, 2) | 
|  | 1352 | + | 
|  | 1353 | +	var ( | 
|  | 1354 | +		outputSum   int64 | 
|  | 1355 | +		foundChange bool | 
|  | 1356 | +	) | 
|  | 1357 | +	for _, txOut := range tx.TxOut { | 
|  | 1358 | +		outputSum += txOut.Value | 
|  | 1359 | +		if txOut.Value != change.Value { | 
|  | 1360 | +			continue | 
|  | 1361 | +		} | 
|  | 1362 | + | 
|  | 1363 | +		if !bytes.Equal(txOut.PkScript, change.PkScript) { | 
|  | 1364 | +			continue | 
|  | 1365 | +		} | 
|  | 1366 | + | 
|  | 1367 | +		foundChange = true | 
|  | 1368 | +	} | 
|  | 1369 | + | 
|  | 1370 | +	require.True(t, foundChange) | 
|  | 1371 | + | 
|  | 1372 | +	totalInput := int64(group[0].Value) | 
|  | 1373 | +	require.LessOrEqual(t, outputSum, totalInput) | 
|  | 1374 | + | 
|  | 1375 | +	expectedFee := btcutil.Amount(totalInput - outputSum) | 
|  | 1376 | +	require.Greater(t, expectedFee, btcutil.Amount(0)) | 
|  | 1377 | + | 
|  | 1378 | +	txHash := tx.TxHash() | 
|  | 1379 | +	spendDetail := &chainntnfs.SpendDetail{ | 
|  | 1380 | +		SpentOutPoint:     &op, | 
|  | 1381 | +		SpendingTx:        tx, | 
|  | 1382 | +		SpenderTxHash:     &txHash, | 
|  | 1383 | +		SpenderInputIndex: 0, | 
|  | 1384 | +		SpendingHeight:    spendReg.HeightHint + 1, | 
|  | 1385 | +	} | 
|  | 1386 | +	lnd.SpendChannel <- spendDetail | 
|  | 1387 | + | 
|  | 1388 | +	spend := <-spendChan | 
|  | 1389 | +	require.Equal(t, expectedFee, spend.OnChainFeePortion) | 
|  | 1390 | + | 
|  | 1391 | +	confReg := <-lnd.RegisterConfChannel | 
|  | 1392 | +	require.True(t, bytes.Equal(tx.TxOut[0].PkScript, confReg.PkScript) || | 
|  | 1393 | +		bytes.Equal(tx.TxOut[1].PkScript, confReg.PkScript)) | 
|  | 1394 | + | 
|  | 1395 | +	require.NoError( | 
|  | 1396 | +		t, lnd.NotifyHeight(spendReg.HeightHint+batchConfHeight+1), | 
|  | 1397 | +	) | 
|  | 1398 | +	lnd.ConfChannel <- &chainntnfs.TxConfirmation{Tx: tx} | 
|  | 1399 | + | 
|  | 1400 | +	require.Eventually(t, func() bool { | 
|  | 1401 | +		select { | 
|  | 1402 | +		case <-presignedHelper.cleanupCalled: | 
|  | 1403 | +			return true | 
|  | 1404 | +		default: | 
|  | 1405 | +			return false | 
|  | 1406 | +		} | 
|  | 1407 | +	}, test.Timeout, eventuallyCheckFrequency) | 
|  | 1408 | + | 
|  | 1409 | +	conf := <-confChan | 
|  | 1410 | +	require.Equal(t, expectedFee, conf.OnChainFeePortion) | 
|  | 1411 | +} | 
|  | 1412 | + | 
| 1271 | 1413 | // testPresigned_presigned_group_with_identical_change_pkscript tests passing multiple sweeps to | 
| 1272 | 1414 | // the method PresignSweepsGroup. It tests that a change output of a primary | 
| 1273 | 1415 | // deposit sweep is properly added to the presigned transaction. | 
| @@ -2356,6 +2498,10 @@ func TestPresigned(t *testing.T) { | 
| 2356 | 2498 | 		testPresigned_presigned_group_with_change(t, NewStoreMock()) | 
| 2357 | 2499 | 	}) | 
| 2358 | 2500 | 
 | 
|  | 2501 | +	t.Run("fee_portion_change", func(t *testing.T) { | 
|  | 2502 | +		testPresigned_fee_portion_with_change(t, NewStoreMock()) | 
|  | 2503 | +	}) | 
|  | 2504 | + | 
| 2359 | 2505 | 	t.Run("identical change pkscript", func(t *testing.T) { | 
| 2360 | 2506 | 		testPresigned_presigned_group_with_identical_change_pkscript(t, NewStoreMock()) | 
| 2361 | 2507 | 	}) | 
|  | 
0 commit comments