From c87bd5dc38585806f2c02d1654c8273b060cb1d7 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Thu, 17 Jul 2025 15:08:11 +0200 Subject: [PATCH 1/5] utils: function to get dust limit for a pkscript. --- utils/dust_limit.go | 15 +++++++++++++++ utils/dust_limit_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 utils/dust_limit.go create mode 100644 utils/dust_limit_test.go diff --git a/utils/dust_limit.go b/utils/dust_limit.go new file mode 100644 index 000000000..ba9b567a0 --- /dev/null +++ b/utils/dust_limit.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/mempool" + "github.com/btcsuite/btcd/wire" +) + +// DustLimitForPkScript returns the dust limit for a given pkScript. An output +// must be greater or equal to this value. +func DustLimitForPkScript(pkscript []byte) btcutil.Amount { + return btcutil.Amount(mempool.GetDustThreshold(&wire.TxOut{ + PkScript: pkscript, + })) +} diff --git a/utils/dust_limit_test.go b/utils/dust_limit_test.go new file mode 100644 index 000000000..be6d84bbd --- /dev/null +++ b/utils/dust_limit_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "testing" + + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/stretchr/testify/require" +) + +type pkScriptGetter func([]byte) ([]byte, error) + +// TestDustLimitForPkScript checks that the dust limit for a given script size +// matches the calculation in lnwallet.DustLimitForSize. +func TestDustLimitForPkScript(t *testing.T) { + getScripts := map[int]pkScriptGetter{ + input.P2WPKHSize: input.WitnessPubKeyHash, + input.P2WSHSize: input.WitnessScriptHash, + input.P2SHSize: input.GenerateP2SH, + input.P2PKHSize: input.GenerateP2PKH, + } + + for scriptSize, getPkScript := range getScripts { + pkScript, err := getPkScript([]byte{}) + require.NoError(t, err, "failed to generate pkScript") + + require.Equal( + t, lnwallet.DustLimitForSize(scriptSize), + DustLimitForPkScript(pkScript), + ) + } +} From 6bedd9ea4b42016cba281f82c2f7b9f1335cc40e Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Fri, 18 Jul 2025 11:05:01 +0200 Subject: [PATCH 2/5] sweepbatcher: optional change in sweep struct This commit adds an optional change output to the sweep struct. --- sweepbatcher/sweep_batch.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index 933077fcf..c4c464c84 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -125,6 +125,9 @@ type sweep struct { // presigned is set, if the sweep should be handled in presigned mode. presigned bool + + // change is the optional change output of the sweep. + change *wire.TxOut } // batchState is the state of the batch. From 4cb5c2ce1dba4a473b053b0026d2d10af757f05f Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Fri, 18 Jul 2025 11:06:42 +0200 Subject: [PATCH 3/5] sweepbatcher: consider optional change in greedy selection --- sweepbatcher/greedy_batch_selection.go | 7 ++ sweepbatcher/greedy_batch_selection_test.go | 88 +++++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/sweepbatcher/greedy_batch_selection.go b/sweepbatcher/greedy_batch_selection.go index 1d6726e86..8b910dbcb 100644 --- a/sweepbatcher/greedy_batch_selection.go +++ b/sweepbatcher/greedy_batch_selection.go @@ -210,6 +210,13 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) { err) } + // Add change output weights. + for _, s := range batch.sweeps { + if s.change != nil { + weight.AddOutput(s.change.PkScript) + } + } + // Add inputs. for _, sweep := range batch.sweeps { if sweep.nonCoopHint || sweep.coopFailed { diff --git a/sweepbatcher/greedy_batch_selection_test.go b/sweepbatcher/greedy_batch_selection_test.go index cc47f1ad5..33b0d5add 100644 --- a/sweepbatcher/greedy_batch_selection_test.go +++ b/sweepbatcher/greedy_batch_selection_test.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/loop/swap" "github.com/lightningnetwork/lnd/input" @@ -16,24 +17,28 @@ import ( // Useful constants for tests. const ( - lowFeeRate = chainfee.FeePerKwFloor - highFeeRate = chainfee.SatPerKWeight(30000) + lowFeeRate = chainfee.FeePerKwFloor + mediumFeeRate = lowFeeRate + 200 + highFeeRate = chainfee.SatPerKWeight(30000) coopInputWeight = lntypes.WeightUnit(230) + batchOutputWeight = lntypes.WeightUnit(343) nonCoopInputWeight = lntypes.WeightUnit(393) nonCoopPenalty = nonCoopInputWeight - coopInputWeight coopNewBatchWeight = lntypes.WeightUnit(444) nonCoopNewBatchWeight = coopNewBatchWeight + nonCoopPenalty + changeOutputWeight = lntypes.WeightUnit(input.P2TROutputSize) // p2pkhDiscount is weight discount P2PKH output has over P2TR output. p2pkhDiscount = lntypes.WeightUnit( input.P2TROutputSize-input.P2PKHOutputSize, ) * 4 - coopTwoSweepBatchWeight = coopNewBatchWeight + coopInputWeight - nonCoopTwoSweepBatchWeight = coopTwoSweepBatchWeight + 2*nonCoopPenalty - v2v3BatchWeight = nonCoopTwoSweepBatchWeight - 25 - mixedTwoSweepBatchWeight = coopTwoSweepBatchWeight + nonCoopPenalty + coopTwoSweepBatchWeight = coopNewBatchWeight + coopInputWeight + coopSingleSweepChangeBatchWeight = coopInputWeight + batchOutputWeight + changeOutputWeight + nonCoopTwoSweepBatchWeight = coopTwoSweepBatchWeight + 2*nonCoopPenalty + v2v3BatchWeight = nonCoopTwoSweepBatchWeight - 25 + mixedTwoSweepBatchWeight = coopTwoSweepBatchWeight + nonCoopPenalty ) // testHtlcV2SuccessEstimator adds weight of non-cooperative input to estimator @@ -265,6 +270,13 @@ func TestEstimateBatchWeight(t *testing.T) { se3 := testHtlcV3SuccessEstimator trAddr := (*btcutil.AddressTaproot)(nil) + changeAddr := "bc1pdx9ggvtjjcpaqfqk375qhdmzx9xu8dcu7w94lqfcxhh0rj" + + "lwyyeq5ryn6r" + changeAddress, err := btcutil.DecodeAddress(changeAddr, nil) + require.NoError(t, err) + changePkscript, err := txscript.PayToAddrScript(changeAddress) + require.NoError(t, err) + cases := []struct { name string batch *batch @@ -290,6 +302,29 @@ func TestEstimateBatchWeight(t *testing.T) { }, }, + { + name: "one sweep regular batch with change", + batch: &batch{ + id: 1, + rbfCache: rbfCache{ + FeeRate: lowFeeRate, + }, + sweeps: map[wire.OutPoint]sweep{ + outpoint1: { + htlcSuccessEstimator: se3, + change: &wire.TxOut{ + PkScript: changePkscript, + }, + }, + }, + }, + wantBatchFeeDetails: feeDetails{ + BatchId: 1, + FeeRate: lowFeeRate, + Weight: coopSingleSweepChangeBatchWeight, + }, + }, + { name: "two sweeps regular batch", batch: &batch{ @@ -778,6 +813,47 @@ func TestSelectBatches(t *testing.T) { }, wantBestBatchesIds: []int32{1, newBatchSignal}, }, + + { + name: "low fee change sweep, placed in new batch", + batches: []feeDetails{ + { + BatchId: 1, + FeeRate: mediumFeeRate, + Weight: coopNewBatchWeight, + }, + }, + sweep: feeDetails{ + FeeRate: lowFeeRate, + Weight: coopInputWeight + changeOutputWeight, + }, + oneSweepBatch: feeDetails{ + FeeRate: lowFeeRate, + Weight: coopNewBatchWeight, + }, + wantBestBatchesIds: []int32{newBatchSignal, 1}, + }, + + { + name: "high fee change sweep, placed in existing " + + "medium batch", + batches: []feeDetails{ + { + BatchId: 1, + FeeRate: mediumFeeRate, + Weight: coopNewBatchWeight, + }, + }, + sweep: feeDetails{ + FeeRate: highFeeRate, + Weight: coopInputWeight + changeOutputWeight, + }, + oneSweepBatch: feeDetails{ + FeeRate: highFeeRate, + Weight: coopNewBatchWeight, + }, + wantBestBatchesIds: []int32{newBatchSignal, 1}, + }, } for _, tc := range cases { From 26c8b278ece6ae056db4a03fccb8ea1784f101b3 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Fri, 18 Jul 2025 11:07:40 +0200 Subject: [PATCH 4/5] sweepbatcher: consider change in presigning and batch tx Presigning sweeps takes change outputs into account. Each primary deposit id of a sweep group points to an optional change output. sweepbatcher.presign scans all passed sweeps for change outputs and passes them to constructUnsignedTx. Optional change of a swap is encoded in its sweeps as a pointer to the same change output. This change is taken into account when constructing the unsigned batch transaction when it comes to tx weight and outputs. --- sweepbatcher/presigned.go | 39 ++- sweepbatcher/presigned_test.go | 151 +++++++- sweepbatcher/sweep_batch.go | 94 ++++- sweepbatcher/sweep_batch_test.go | 235 ++++++++++++- sweepbatcher/sweep_batcher.go | 19 +- sweepbatcher/sweep_batcher_presigned_test.go | 343 ++++++++++++++++++- sweepbatcher/sweep_batcher_test.go | 116 +++---- 7 files changed, 893 insertions(+), 104 deletions(-) diff --git a/sweepbatcher/presigned.go b/sweepbatcher/presigned.go index 385f34cf9..d49506e2f 100644 --- a/sweepbatcher/presigned.go +++ b/sweepbatcher/presigned.go @@ -51,6 +51,7 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep, outpoint: s.outpoint, value: s.value, presigned: s.presigned, + change: s.change, } } @@ -493,10 +494,12 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error, signedFeeRate := chainfee.NewSatPerKWeight(fee, realWeight) numSweeps := len(tx.TxIn) + numChange := len(tx.TxOut) - 1 b.Infof("attempting to publish custom signed tx=%v, desiredFeerate=%v,"+ - " signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, destAddr=%s", + " signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, "+ + "changeOutputs=%d, destAddr=%s", txHash, feeRate, signedFeeRate, realWeight, fee, numSweeps, - address) + numChange, address) b.debugLogTx("serialized batch", tx) // Publish the transaction. @@ -593,23 +596,31 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount, } // Compare outputs. - if len(unsignedTx.TxOut) != 1 { - return fmt.Errorf("unsigned tx has %d outputs, want 1", - len(unsignedTx.TxOut)) - } - if len(signedTx.TxOut) != 1 { - return fmt.Errorf("the signed tx has %d outputs, want 1", + if len(unsignedTx.TxOut) != len(signedTx.TxOut) { + return fmt.Errorf("unsigned tx has %d outputs, signed tx has "+ + "%d outputs, should be equal", len(unsignedTx.TxOut), len(signedTx.TxOut)) } - unsignedOut := unsignedTx.TxOut[0] - signedOut := signedTx.TxOut[0] - if !bytes.Equal(unsignedOut.PkScript, signedOut.PkScript) { - return fmt.Errorf("mismatch of output pkScript: %x, %x", - unsignedOut.PkScript, signedOut.PkScript) + for i, o := range unsignedTx.TxOut { + if !bytes.Equal(o.PkScript, signedTx.TxOut[i].PkScript) { + return fmt.Errorf("mismatch of output pkScript: %x, %x", + o.PkScript, signedTx.TxOut[i].PkScript) + } + if i != 0 && o.Value != signedTx.TxOut[i].Value { + return fmt.Errorf("mismatch of output value: %d, %d", + o.Value, signedTx.TxOut[i].Value) + } + } + + // Calculate the total value of all outputs to help determine the + // transaction fee. + totalOutputValue := btcutil.Amount(0) + for _, o := range signedTx.TxOut { + totalOutputValue += btcutil.Amount(o.Value) } // Find the feerate of signedTx. - fee := inputAmt - btcutil.Amount(signedOut.Value) + fee := inputAmt - totalOutputValue weight := lntypes.WeightUnit( blockchain.GetTransactionWeight(btcutil.NewTx(signedTx)), ) diff --git a/sweepbatcher/presigned_test.go b/sweepbatcher/presigned_test.go index 629ff1de4..d4f593737 100644 --- a/sweepbatcher/presigned_test.go +++ b/sweepbatcher/presigned_test.go @@ -1460,7 +1460,8 @@ func TestCheckSignedTx(t *testing.T) { }, inputAmt: 3_000_000, minRelayFee: 253, - wantErr: "unsigned tx has 2 outputs, want 1", + wantErr: "unsigned tx has 2 outputs, signed tx " + + "has 1 outputs, should be equal", }, { @@ -1517,7 +1518,153 @@ func TestCheckSignedTx(t *testing.T) { }, inputAmt: 3_000_000, minRelayFee: 253, - wantErr: "the signed tx has 2 outputs, want 1", + wantErr: "unsigned tx has 1 outputs, signed tx " + + "has 2 outputs, should be equal", + }, + + { + name: "pkscript mismatch", + unsignedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 2999374, + PkScript: batchPkScript, + }, + }, + LockTime: 800_000, + }, + signedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + Witness: wire.TxWitness{ + []byte("test"), + }, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 2999374, + PkScript: []byte{0xaf, 0xfe}, // Just to make it different. + }, + }, + LockTime: 799_999, + }, + inputAmt: 3_000_000, + minRelayFee: 253, + wantErr: "mismatch of output pkScript", + }, + + { + name: "value mismatch, first output", + unsignedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 2999374, + PkScript: batchPkScript, + }, + }, + LockTime: 800_000, + }, + signedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + Witness: wire.TxWitness{ + []byte("test"), + }, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 1_337_000, // Just to make it different. + PkScript: batchPkScript, + }, + }, + LockTime: 799_999, + }, + inputAmt: 3_000_000, + minRelayFee: 253, + wantErr: "", + }, + + { + name: "value mismatch, change output", + unsignedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + }, + { + PreviousOutPoint: op1, + Sequence: 2, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 2999374, + PkScript: batchPkScript, + }, + { + Value: 1_337_000, + PkScript: batchPkScript, + }, + }, + LockTime: 800_000, + }, + signedTx: &wire.MsgTx{ + Version: 2, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + Sequence: 2, + Witness: wire.TxWitness{ + []byte("test"), + }, + }, + { + PreviousOutPoint: op1, + Sequence: 2, + Witness: wire.TxWitness{ + []byte("test"), + }, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 2_493_300, + PkScript: batchPkScript, + }, + { + Value: 1_338, // Just to make it different. + PkScript: batchPkScript, + }, + }, + LockTime: 799_999, + }, + inputAmt: 3_000_000, + minRelayFee: 253, + wantErr: "mismatch of output value", }, { diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index c4c464c84..13c514862 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -26,6 +26,7 @@ import ( "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/swap" sweeppkg "github.com/lightninglabs/loop/sweep" + "github.com/lightninglabs/loop/utils" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/input" @@ -1239,10 +1240,14 @@ func (b *batch) createPsbt(unsignedTx *wire.MsgTx, sweeps []sweep) ([]byte, } // constructUnsignedTx creates unsigned tx from the sweeps, paying to the addr. -// It also returns absolute fee (from weight and clamped). +// It also returns absolute fee (from weight and clamped). The main output is +// the first output of the transaction, followed by an optional list of change +// outputs. If the main output value is below dust limit this function will +// return an error. func constructUnsignedTx(sweeps []sweep, address btcutil.Address, - currentHeight int32, feeRate chainfee.SatPerKWeight) (*wire.MsgTx, - lntypes.WeightUnit, btcutil.Amount, btcutil.Amount, error) { + currentHeight int32, feeRate chainfee.SatPerKWeight) ( + *wire.MsgTx, lntypes.WeightUnit, btcutil.Amount, btcutil.Amount, + error) { // Sanity check, there should be at least 1 sweep in this batch. if len(sweeps) == 0 { @@ -1255,6 +1260,13 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address, LockTime: uint32(currentHeight), } + var changeOutputs []*wire.TxOut + for _, sweep := range sweeps { + if sweep.change != nil { + changeOutputs = append(changeOutputs, sweep.change) + } + } + // Add transaction inputs and estimate its weight. var weightEstimate input.TxWeightEstimator for _, sweep := range sweeps { @@ -1300,6 +1312,11 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address, "failed: %w", err) } + // Add the optional change outputs to weight estimates. + for _, o := range changeOutputs { + weightEstimate.AddOutput(o.PkScript) + } + // Keep track of the total amount this batch is sweeping back. batchAmt := btcutil.Amount(0) for _, sweep := range sweeps { @@ -1317,15 +1334,78 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address, feeForWeight++ } + // Add the batch transaction output, which excludes the fees paid to + // miners. Reduce the amount by the sum of change outputs, if any. + var sumChange int64 + for _, change := range changeOutputs { + sumChange += change.Value + } + + // Ensure that the batch amount is greater than the sum of change. + if batchAmt <= btcutil.Amount(sumChange) { + return nil, 0, 0, 0, fmt.Errorf("batch amount %v is <= the "+ + "sum of change outputs %v", batchAmt, + btcutil.Amount(sumChange)) + } + // Clamp the calculated fee to the max allowed fee amount for the batch. - fee := clampBatchFee(feeForWeight, batchAmt) + fee := clampBatchFee(feeForWeight, batchAmt-btcutil.Amount(sumChange)) - // Add the batch transaction output, which excludes the fees paid to - // miners. + // Ensure that batch amount exceeds the sum of change outputs and the + // fee, and that it is also greater than dust limit for the main + // output. + dustLimit := utils.DustLimitForPkScript(batchPkScript) + if fee+btcutil.Amount(sumChange)+dustLimit > batchAmt { + return nil, 0, 0, 0, fmt.Errorf("batch amount %v is <= the "+ + "sum of change outputs %v plus fee %v and dust "+ + "limit %v", batchAmt, btcutil.Amount(sumChange), + fee, dustLimit) + } + + // Add the main output first. batchTx.AddTxOut(&wire.TxOut{ PkScript: batchPkScript, - Value: int64(batchAmt - fee), + Value: int64(batchAmt-fee) - sumChange, }) + // Then add change outputs. + for _, txOut := range changeOutputs { + batchTx.AddTxOut(&wire.TxOut{ + PkScript: txOut.PkScript, + Value: txOut.Value, + }) + } + + // Check that for each swap, inputs exceed the change outputs. + if len(changeOutputs) != 0 { + swap2Inputs := make(map[lntypes.Hash]btcutil.Amount) + swap2Change := make(map[lntypes.Hash]btcutil.Amount) + for _, sweep := range sweeps { + swap2Inputs[sweep.swapHash] += sweep.value + if sweep.change != nil { + swap2Change[sweep.swapHash] += + btcutil.Amount(sweep.change.Value) + } + } + + for swapHash, inputs := range swap2Inputs { + change := swap2Change[swapHash] + if inputs <= change { + return nil, 0, 0, 0, fmt.Errorf(""+ + "inputs %v <= change %v for swap %x", + inputs, change, swapHash[:6]) + } + } + } + + // Ensure that each output is above dust limit. + for _, txOut := range batchTx.TxOut { + dustLimit = utils.DustLimitForPkScript(txOut.PkScript) + if btcutil.Amount(txOut.Value) < dustLimit { + return nil, 0, 0, 0, fmt.Errorf("output %v is below "+ + "dust limit %v", btcutil.Amount(txOut.Value), + dustLimit) + } + } return batchTx, weight, feeForWeight, fee, nil } diff --git a/sweepbatcher/sweep_batch_test.go b/sweepbatcher/sweep_batch_test.go index 570565b0d..fc77401a5 100644 --- a/sweepbatcher/sweep_batch_test.go +++ b/sweepbatcher/sweep_batch_test.go @@ -13,6 +13,7 @@ import ( "github.com/lightninglabs/loop/utils" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" ) @@ -29,6 +30,10 @@ func TestConstructUnsignedTx(t *testing.T) { Hash: chainhash.Hash{2, 2, 2}, Index: 2, } + op3 := wire.OutPoint{ + Hash: chainhash.Hash{3, 3, 3}, + Index: 3, + } batchPkScript, err := txscript.PayToAddrScript(destAddr) require.NoError(t, err) @@ -40,6 +45,28 @@ func TestConstructUnsignedTx(t *testing.T) { p2trPkScript, err := txscript.PayToAddrScript(p2trAddress) require.NoError(t, err) + change1Addr := "bc1pdx9ggvtjjcpaqfqk375qhdmzx9xu8dcu7w94lqfcxhh0rj" + + "lwyyeq5ryn6r" + change1Address, err := btcutil.DecodeAddress(change1Addr, nil) + require.NoError(t, err) + change1Pkscript, err := txscript.PayToAddrScript(change1Address) + require.NoError(t, err) + change1 := &wire.TxOut{ + Value: 100_000, + PkScript: change1Pkscript, + } + + change2Addr := "bc1psw0nrrulq4pgyuyk09a3wsutygltys4gxjjw3zl2uz4ep8pa" + + "r2vsvntfe0" + change2Address, err := btcutil.DecodeAddress(change2Addr, nil) + require.NoError(t, err) + change2Pkscript, err := txscript.PayToAddrScript(change2Address) + require.NoError(t, err) + change2 := &wire.TxOut{ + Value: 200_000, + PkScript: change2Pkscript, + } + serializedPubKey := []byte{ 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, @@ -70,6 +97,8 @@ func TestConstructUnsignedTx(t *testing.T) { return fmt.Errorf("weight estimator test failure") } + dustLimit := lnwallet.DustLimitForSize(input.P2TRSize) + cases := []struct { name string sweeps []sweep @@ -223,7 +252,7 @@ func TestConstructUnsignedTx(t *testing.T) { }, TxOut: []*wire.TxOut{ { - Value: 2400000, + Value: 2_400_000, PkScript: batchPkScript, }, }, @@ -265,7 +294,7 @@ func TestConstructUnsignedTx(t *testing.T) { }, TxOut: []*wire.TxOut{ { - Value: 2999211, + Value: 2_999_211, PkScript: batchPkScript, }, }, @@ -275,6 +304,208 @@ func TestConstructUnsignedTx(t *testing.T) { wantFee: 789, }, + { + name: "single sweep with change", + sweeps: []sweep{ + { + outpoint: op1, + value: 1_000_000, + change: change1, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 1000, + wantTx: &wire.MsgTx{ + Version: 2, + LockTime: 800_000, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op1, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 899_384, + PkScript: p2trPkScript, + }, + { + Value: change1.Value, + PkScript: change1.PkScript, + }, + }, + }, + wantWeight: 616, + wantFeeForWeight: 616, + wantFee: 616, + }, + + { + name: "all sweeps different change outputs", + sweeps: []sweep{ + { + outpoint: op1, + value: 1_000_000, + }, + { + outpoint: op2, + value: 2_000_000, + change: change1, + }, + { + outpoint: op3, + value: 3_000_000, + change: change2, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 1000, + wantTx: &wire.MsgTx{ + Version: 2, + LockTime: 800_000, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op1, + }, + { + PreviousOutPoint: op2, + }, + { + PreviousOutPoint: op3, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 5_698_752, + PkScript: p2trPkScript, + }, + { + Value: change1.Value, + PkScript: change1.PkScript, + }, + { + Value: change2.Value, + PkScript: change2.PkScript, + }, + }, + }, + wantWeight: 1248, + wantFeeForWeight: 1248, + wantFee: 1248, + }, + + { + name: "change exceeds input value", + sweeps: []sweep{ + { + outpoint: op2, + value: btcutil.Amount(change1.Value - 1), + change: change1, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 1000, + wantTx: &wire.MsgTx{ + Version: 2, + LockTime: 800_000, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op2, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: change1.Value, + PkScript: change1.PkScript, + }, + }, + }, + wantErr: "batch amount 0.00099999 BTC is <= the sum " + + "of change outputs 0.00100000 BTC", + }, + + { + name: "main output dust, batch amount less than " + + "change+fee+dust", + sweeps: []sweep{ + { + outpoint: op1, + value: dustLimit, + }, + { + outpoint: op2, + value: btcutil.Amount(change1.Value), + change: change1, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 1, + wantErr: "batch amount 0.00100330 BTC is <= the sum " + + "of change outputs 0.00100000 BTC plus fee " + + "0.00000001 BTC and dust limit 0.00000330 BTC", + }, + + { + name: "change output is dust", + sweeps: []sweep{ + { + outpoint: op1, + value: 1_000_000, + change: &wire.TxOut{ + Value: int64(dustLimit - 1), + PkScript: []byte{0xaf, 0xfe}, + }, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 1, + wantErr: "output 0.00000329 BTC is below dust limit " + + "0.00000477 BTC", + }, + + { + name: "clamp fee to max fee to swap amount ratio", + sweeps: []sweep{ + { + outpoint: op1, + value: btcutil.SatoshiPerBitcoin, + change: &wire.TxOut{ + Value: btcutil.SatoshiPerBitcoin * 0.9, + PkScript: change1Pkscript, + }, + }, + }, + address: p2trAddress, + currentHeight: 800_000, + feeRate: 10000000, + wantTx: &wire.MsgTx{ + Version: 2, + LockTime: 800_000, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: op1, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: btcutil.SatoshiPerBitcoin * 0.08, + PkScript: p2trPkScript, + }, + { + Value: btcutil.SatoshiPerBitcoin * 0.9, + PkScript: change1.PkScript, + }, + }, + }, + wantWeight: 616, + wantFeeForWeight: 6_160_000, + wantFee: 2_000_000, + }, + { name: "weight estimator fails", sweeps: []sweep{ diff --git a/sweepbatcher/sweep_batcher.go b/sweepbatcher/sweep_batcher.go index 6bab035e7..821ed11a6 100644 --- a/sweepbatcher/sweep_batcher.go +++ b/sweepbatcher/sweep_batcher.go @@ -125,6 +125,9 @@ type SweepInfo struct { // value should be stable for a sweep. Currently presigned and // non-presigned sweeps never appear in the same batch. IsPresigned bool + + // Change is an optional change output of the sweep. + Change *wire.TxOut } // SweepFetcher is used to get details of a sweep. @@ -168,7 +171,10 @@ type PresignedHelper interface { // SignTx signs an unsigned transaction or returns a pre-signed tx. // It must satisfy the following invariants: // - the set of inputs is the same, though the order may change; - // - the output is the same, but its amount may be different; + // - the main output is the same, but its amount may be different; + // - the main output is the first output in the transaction; + // - an optional set of change outputs may be added, the values and + // pkscripts must be preserved. // - feerate is higher or equal to minRelayFee; // - LockTime may be decreased; // - transaction version must be the same; @@ -177,6 +183,7 @@ type PresignedHelper interface { // When choosing a presigned transaction, a transaction with fee rate // closer to the fee rate passed is selected. If loadOnly is set, it // doesn't try to sign the transaction and only loads a presigned tx. + // These rules are enforced by CheckSignedTx function. SignTx(ctx context.Context, primarySweepID wire.OutPoint, tx *wire.MsgTx, inputAmt btcutil.Amount, minRelayFee, feeRate chainfee.SatPerKWeight, @@ -711,9 +718,11 @@ func (b *Batcher) Run(ctx context.Context) error { // group. This method must be called prior to AddSweep if presigned mode is // enabled, otherwise AddSweep will fail. All the sweeps must belong to the same // swap. The order of sweeps is important. The first sweep serves as -// primarySweepID if the group starts a new batch. +// primarySweepID if the group starts a new batch. The change output may be nil +// to indicate that the sweep group does not create a change output. func (b *Batcher) PresignSweepsGroup(ctx context.Context, inputs []Input, - sweepTimeout int32, destAddress btcutil.Address) error { + sweepTimeout int32, destAddress btcutil.Address, + changeOutput *wire.TxOut) error { if len(inputs) == 0 { return fmt.Errorf("no inputs passed to PresignSweepsGroup") @@ -745,6 +754,9 @@ func (b *Batcher) PresignSweepsGroup(ctx context.Context, inputs []Input, } } + // Set the change output on the primary group sweep. + sweeps[0].change = changeOutput + // The sweeps are ordered inside the group, the first one is the primary // outpoint in the batch. primarySweepID := sweeps[0].outpoint @@ -1564,6 +1576,7 @@ func (b *Batcher) loadSweep(ctx context.Context, swapHash lntypes.Hash, minFeeRate: minFeeRate, nonCoopHint: s.NonCoopHint, presigned: s.IsPresigned, + change: s.Change, }, nil } diff --git a/sweepbatcher/sweep_batcher_presigned_test.go b/sweepbatcher/sweep_batcher_presigned_test.go index 36f11f750..fd28bc7d8 100644 --- a/sweepbatcher/sweep_batcher_presigned_test.go +++ b/sweepbatcher/sweep_batcher_presigned_test.go @@ -17,7 +17,9 @@ import ( "github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,6 +32,9 @@ type mockPresignedHelper struct { // participating in presigning. onlineOutpoints map[wire.OutPoint]bool + // changeOutputs is a map of change outputs for a given primary deposit. + changeOutputs map[wire.OutPoint]*wire.TxOut + // presignedBatches is the collection of presigned batches. The key is // primarySweepID. presignedBatches map[wire.OutPoint][]*wire.MsgTx @@ -46,6 +51,7 @@ type mockPresignedHelper struct { func newMockPresignedHelper() *mockPresignedHelper { return &mockPresignedHelper{ onlineOutpoints: make(map[wire.OutPoint]bool), + changeOutputs: make(map[wire.OutPoint]*wire.TxOut), presignedBatches: make(map[wire.OutPoint][]*wire.MsgTx), cleanupCalled: make(chan struct{}), } @@ -59,6 +65,16 @@ func (h *mockPresignedHelper) SetOutpointOnline(op wire.OutPoint, online bool) { h.onlineOutpoints[op] = online } +// setChangeForPrimaryDeposit sets the change output of a primary deposit sweep. +func (h *mockPresignedHelper) setChangeForPrimaryDeposit(op wire.OutPoint, + change *wire.TxOut) { + + h.mu.Lock() + defer h.mu.Unlock() + + h.changeOutputs[op] = change +} + // offlineInputs returns inputs of a tx which are offline. func (h *mockPresignedHelper) offlineInputs(tx *wire.MsgTx) []wire.OutPoint { offline := make([]wire.OutPoint, 0, len(tx.TxIn)) @@ -113,7 +129,7 @@ func (h *mockPresignedHelper) DestPkScript(ctx context.Context, } // SignTx tries to sign the transaction. If all the inputs are online, it signs -// the exact transaction passed and adds it to presignedBatches. Otherwise it +// the exact transaction passed and adds it to presignedBatches. Otherwise, it // looks for a transaction in presignedBatches satisfying the criteria. func (h *mockPresignedHelper) SignTx(ctx context.Context, primarySweepID wire.OutPoint, tx *wire.MsgTx, inputAmt btcutil.Amount, @@ -211,6 +227,9 @@ func (h *mockPresignedHelper) FetchSweep(_ context.Context, // Find IsPresigned. _, isPresigned := h.onlineOutpoints[utxo] + // Find change. + change := h.changeOutputs[utxo] + return &SweepInfo{ // Set Timeout to prevent warning messages about timeout=0. Timeout: sweepTimeout, @@ -220,6 +239,7 @@ func (h *mockPresignedHelper) FetchSweep(_ context.Context, HTLC: swap.Htlc{ PkScript: []byte{10, 11, 12}, }, + Change: change, }, nil } @@ -273,7 +293,7 @@ func testPresigned_forgotten_presign(t *testing.T, presignedHelper.SetOutpointOnline(op1, false) err := batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: 1_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.Error(t, err) require.ErrorContains(t, err, "offline") @@ -350,7 +370,7 @@ func testPresigned_input1_offline_then_input2(t *testing.T, presignedHelper.SetOutpointOnline(op1, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: 1_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) @@ -413,7 +433,7 @@ func testPresigned_input1_offline_then_input2(t *testing.T, presignedHelper.SetOutpointOnline(op2, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op2, Value: 2_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) @@ -520,7 +540,7 @@ func testPresigned_min_relay_fee(t *testing.T, presignedHelper.SetOutpointOnline(op1, true) err := batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: inputAmt}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) @@ -644,7 +664,7 @@ func testPresigned_two_inputs_one_goes_offline(t *testing.T, presignedHelper.SetOutpointOnline(op1, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: 1_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) require.NoError(t, batcher.AddSweep(ctx, &sweepReq1)) @@ -670,7 +690,7 @@ func testPresigned_two_inputs_one_goes_offline(t *testing.T, presignedHelper.SetOutpointOnline(op2, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op2, Value: 2_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) require.NoError(t, batcher.AddSweep(ctx, &sweepReq2)) @@ -780,7 +800,7 @@ func testPresigned_first_publish_fails(t *testing.T, presignedHelper.SetOutpointOnline(op1, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: 1_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) presignedHelper.SetOutpointOnline(op1, false) @@ -903,7 +923,7 @@ func testPresigned_locktime(t *testing.T, presignedHelper.SetOutpointOnline(op1, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op1, Value: 1_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) presignedHelper.SetOutpointOnline(op1, false) @@ -994,14 +1014,18 @@ func testPresigned_presigned_group(t *testing.T, presignedHelper.SetOutpointOnline(op2, false) // An attempt to presign must fail. - err = batcher.PresignSweepsGroup(ctx, group1, sweepTimeout, destAddr) + err = batcher.PresignSweepsGroup( + ctx, group1, sweepTimeout, destAddr, nil, + ) require.ErrorContains(t, err, "some outpoint is offline") // Enable both outpoints. presignedHelper.SetOutpointOnline(op2, true) // An attempt to presign must succeed. - err = batcher.PresignSweepsGroup(ctx, group1, sweepTimeout, destAddr) + err = batcher.PresignSweepsGroup( + ctx, group1, sweepTimeout, destAddr, nil, + ) require.NoError(t, err) // Add the sweep, triggering the publish attempt. @@ -1053,7 +1077,9 @@ func testPresigned_presigned_group(t *testing.T, presignedHelper.SetOutpointOnline(op4, true) // An attempt to presign must succeed. - err = batcher.PresignSweepsGroup(ctx, group2, sweepTimeout, destAddr) + err = batcher.PresignSweepsGroup( + ctx, group2, sweepTimeout, destAddr, nil, + ) require.NoError(t, err) // Add the sweep. It should go to the same batch. @@ -1107,7 +1133,9 @@ func testPresigned_presigned_group(t *testing.T, presignedHelper.SetOutpointOnline(op6, true) // An attempt to presign must succeed. - err = batcher.PresignSweepsGroup(ctx, group3, sweepTimeout, destAddr) + err = batcher.PresignSweepsGroup( + ctx, group3, sweepTimeout, destAddr, nil, + ) require.NoError(t, err) // Add the sweep. It should go to the same batch. @@ -1135,6 +1163,271 @@ func testPresigned_presigned_group(t *testing.T, require.Equal(t, batchPkScript, tx.TxOut[0].PkScript) } +// testPresigned_presigned_group_with_change tests passing multiple sweeps to +// the method PresignSweepsGroup. It tests that a change output of a primary +// deposit sweep is properly added to the presigned transaction. +func testPresigned_presigned_group_with_change(t *testing.T, + batcherStore testBatcherStore) { + + defer test.Guard(t)() + + batchPkScript, err := txscript.PayToAddrScript(destAddr) + require.NoError(t, err) + + lnd := test.NewMockLnd() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + customFeeRate := func(_ context.Context, _ lntypes.Hash, + _ wire.OutPoint) (chainfee.SatPerKWeight, error) { + + return chainfee.SatPerKWeight(10_000), nil + } + + presignedHelper := newMockPresignedHelper() + + batcher := NewBatcher( + lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, presignedHelper, + WithCustomFeeRate(customFeeRate), + WithPresignedHelper(presignedHelper), + ) + + go func() { + err := batcher.Run(ctx) + checkBatcherError(t, err) + }() + + // Create a swap of two sweeps. + swapHash1 := lntypes.Hash{1, 1, 1} + op1 := wire.OutPoint{ + Hash: chainhash.Hash{1, 1}, + Index: 1, + } + op2 := wire.OutPoint{ + Hash: chainhash.Hash{2, 2}, + Index: 2, + } + group1 := []Input{ + { + Outpoint: op1, + Value: 1_000_000, + }, + { + Outpoint: op2, + Value: 2_000_000, + }, + } + change := &wire.TxOut{ + Value: 500_000, + PkScript: []byte{0xaf, 0xfe}, + } + + presignedHelper.setChangeForPrimaryDeposit(op1, change) + + // Enable only one of the sweeps. + presignedHelper.SetOutpointOnline(op1, true) + presignedHelper.SetOutpointOnline(op2, true) + + // An attempt to presign must fail. + err = batcher.PresignSweepsGroup( + ctx, group1, sweepTimeout, destAddr, change, + ) + require.NoError(t, err) + + // Add the sweep, triggering the publishing attempt. + err = batcher.AddSweep(ctx, &SweepRequest{ + SwapHash: swapHash1, + Inputs: group1, + Notifier: &dummyNotifier, + }) + require.NoError(t, err) + + // Since a batch was created we check that it registered for its primary + // sweep's spend. + <-lnd.RegisterSpendChannel + + // Wait for a transactions to be published. + tx := <-lnd.TxPublishChannel + require.Len(t, tx.TxIn, 2) + require.Len(t, tx.TxOut, 2) + require.ElementsMatch( + t, []wire.OutPoint{op1, op2}, + []wire.OutPoint{ + tx.TxIn[0].PreviousOutPoint, + tx.TxIn[1].PreviousOutPoint, + }, + ) + require.Equal(t, int64(2_493_300), tx.TxOut[0].Value) + require.Equal(t, change.Value, tx.TxOut[1].Value) + require.Equal(t, batchPkScript, tx.TxOut[0].PkScript) + require.Equal(t, change.PkScript, tx.TxOut[1].PkScript) + + // Mine a blocks to trigger republishing. + require.NoError(t, lnd.NotifyHeight(601)) +} + +// testPresigned_presigned_group_with_dust_main_output tests passing multiple +// sweeps to the method PresignSweepsGroup. It tests that a dust change output of +// a primary deposit sweep is rejected by PresignSweepsGroup and AddSweep. +func testPresigned_presigned_group_with_dust_main_output(t *testing.T, + batcherStore testBatcherStore) { + + defer test.Guard(t)() + + lnd := test.NewMockLnd() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + customFeeRate := func(_ context.Context, _ lntypes.Hash, + _ wire.OutPoint) (chainfee.SatPerKWeight, error) { + + return chainfee.SatPerKWeight(10_000), nil + } + + presignedHelper := newMockPresignedHelper() + + batcher := NewBatcher( + lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, presignedHelper, + WithCustomFeeRate(customFeeRate), + WithPresignedHelper(presignedHelper), + ) + + go func() { + err := batcher.Run(ctx) + checkBatcherError(t, err) + }() + + // Create a swap of two sweeps. + swapHash1 := lntypes.Hash{1, 1, 1} + op1 := wire.OutPoint{ + Hash: chainhash.Hash{1, 1}, + Index: 1, + } + inputValue := int64(1_000_000) + group1 := []Input{ + { + Outpoint: op1, + Value: 1_000_000, + }, + } + dustLimit := int64(lnwallet.DustLimitForSize(input.P2TRSize)) + change := &wire.TxOut{ + Value: inputValue - dustLimit + 1, + PkScript: []byte{0xaf, 0xfe}, + } + + presignedHelper.setChangeForPrimaryDeposit(op1, change) + + // Enable only one of the sweeps. + presignedHelper.SetOutpointOnline(op1, true) + + // An attempt to presign must fail. + err := batcher.PresignSweepsGroup( + ctx, group1, sweepTimeout, destAddr, change, + ) + require.EqualError(t, err, "failed to construct unsigned tx for "+ + "feeRate 253 sat/kw: batch amount 0.01000000 BTC is <= the "+ + "sum of change outputs 0.00999671 BTC plus fee "+ + "0.00000065 BTC and dust limit 0.00000294 BTC") + + // Add the sweep, triggering the publishing attempt. + err = batcher.AddSweep(ctx, &SweepRequest{ + SwapHash: swapHash1, + Inputs: group1, + Notifier: &dummyNotifier, + }) + require.ErrorContains(t, err, "were not presigned") +} + +// testPresigned_presigned_group_with_dust_change tests passing multiple sweeps +// to the method PresignSweepsGroup. It tests that a dust change output of a +// primary deposit sweep is rejected by PresignSweepsGroup and AddSweep. +func testPresigned_presigned_group_with_dust_change(t *testing.T, + batcherStore testBatcherStore) { + + defer test.Guard(t)() + + lnd := test.NewMockLnd() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + customFeeRate := func(_ context.Context, _ lntypes.Hash, + _ wire.OutPoint) (chainfee.SatPerKWeight, error) { + + return chainfee.SatPerKWeight(10_000), nil + } + + presignedHelper := newMockPresignedHelper() + + batcher := NewBatcher( + lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, presignedHelper, + WithCustomFeeRate(customFeeRate), + WithPresignedHelper(presignedHelper), + ) + + go func() { + err := batcher.Run(ctx) + checkBatcherError(t, err) + }() + + // Create a swap of two sweeps. + swapHash1 := lntypes.Hash{1, 1, 1} + op1 := wire.OutPoint{ + Hash: chainhash.Hash{1, 1}, + Index: 1, + } + op2 := wire.OutPoint{ + Hash: chainhash.Hash{2, 2}, + Index: 2, + } + group1 := []Input{ + { + Outpoint: op1, + Value: 1_000_000, + }, + { + Outpoint: op2, + Value: 2_000_000, + }, + } + dustLimit := lnwallet.DustLimitForSize(input.P2TRSize) + change := &wire.TxOut{ + Value: int64(dustLimit - 1), + PkScript: []byte{0xaf, 0xfe}, + } + + presignedHelper.setChangeForPrimaryDeposit(op1, change) + + // Enable only one of the sweeps. + presignedHelper.SetOutpointOnline(op1, true) + presignedHelper.SetOutpointOnline(op2, true) + + // An attempt to presign must fail. + err := batcher.PresignSweepsGroup( + ctx, group1, sweepTimeout, destAddr, change, + ) + require.EqualError(t, err, "failed to construct unsigned tx for "+ + "feeRate 253 sat/kw: output 0.00000329 BTC is below dust "+ + "limit 0.00000477 BTC") + + // Add the sweep, triggering the publishing attempt. + err = batcher.AddSweep(ctx, &SweepRequest{ + SwapHash: swapHash1, + Inputs: group1, + Notifier: &dummyNotifier, + }) + require.ErrorContains(t, err, "were not presigned") +} + // wrappedStoreWithPresignedFlag wraps a SweepFetcher store adding IsPresigned // flag to the returned sweeps, taking it from mockPresignedHelper. type wrappedStoreWithPresignedFlag struct { @@ -1304,7 +1597,7 @@ func testPresigned_presigned_and_regular_sweeps(t *testing.T, store testStore, presignedHelper.SetOutpointOnline(op2, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op2, Value: 2_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) require.NoError(t, batcher.AddSweep(ctx, &sweepReq2)) @@ -1399,7 +1692,7 @@ func testPresigned_presigned_and_regular_sweeps(t *testing.T, store testStore, presignedHelper.SetOutpointOnline(op4, true) err = batcher.PresignSweepsGroup( ctx, []Input{{Outpoint: op4, Value: 3_000_000}}, - sweepTimeout, destAddr, + sweepTimeout, destAddr, nil, ) require.NoError(t, err) require.NoError(t, batcher.AddSweep(ctx, &sweepReq4)) @@ -1520,7 +1813,7 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int, // An attempt to presign must succeed. err := batcher.PresignSweepsGroup( - ctx, group, sweepTimeout, destAddr, + ctx, group, sweepTimeout, destAddr, nil, ) require.NoError(t, err) @@ -1584,11 +1877,11 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int, // An attempt to presign must succeed. err := batcher.PresignSweepsGroup( - ctx, group, sweepTimeout, destAddr, + ctx, group, sweepTimeout, destAddr, nil, ) require.NoError(t, err) - // Add the sweep, triggering the publish attempt. + // Add the sweep, triggering the publishing attempt. require.NoError(t, batcher.AddSweep(ctx, &SweepRequest{ SwapHash: swapHash, Inputs: group, @@ -1799,6 +2092,20 @@ func TestPresigned(t *testing.T) { testPresigned_presigned_group(t, NewStoreMock()) }) + t.Run("change", func(t *testing.T) { + testPresigned_presigned_group_with_change(t, NewStoreMock()) + }) + + t.Run("dust_main_output", func(t *testing.T) { + testPresigned_presigned_group_with_dust_main_output( + t, NewStoreMock(), + ) + }) + + t.Run("dust_change", func(t *testing.T) { + testPresigned_presigned_group_with_dust_change(t, NewStoreMock()) + }) + t.Run("presigned_and_regular_sweeps", func(t *testing.T) { runTests(t, testPresigned_presigned_and_regular_sweeps) }) diff --git a/sweepbatcher/sweep_batcher_test.go b/sweepbatcher/sweep_batcher_test.go index 590d83ae4..b56ddf8ea 100644 --- a/sweepbatcher/sweep_batcher_test.go +++ b/sweepbatcher/sweep_batcher_test.go @@ -228,7 +228,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -237,7 +237,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -275,7 +275,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 222, + Value: 2222, Outpoint: op2, }}, Notifier: &dummyNotifier, @@ -284,7 +284,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, swap2 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -321,7 +321,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, sweepReq3 := SweepRequest{ SwapHash: lntypes.Hash{3, 3, 3}, Inputs: []Input{{ - Value: 333, + Value: 3333, Outpoint: op3, }}, Notifier: &dummyNotifier, @@ -330,7 +330,7 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, swap3 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 1, - AmountRequested: 333, + AmountRequested: 3333, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -538,7 +538,7 @@ func testTxLabeler(t *testing.T, store testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -547,7 +547,7 @@ func testTxLabeler(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -691,7 +691,7 @@ func testPublishErrorHandler(t *testing.T, store testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{1, 1}, Index: 1, @@ -703,7 +703,7 @@ func testPublishErrorHandler(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -773,7 +773,7 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, Index: 1, } const ( - inputValue = 111 + inputValue = 1111 outputValue = 50 fee = inputValue - outputValue ) @@ -1208,7 +1208,7 @@ func testSweepBatcherSkippedTxns(t *testing.T, store testStore, } swapHash := lntypes.Hash{1, 1, 1} const ( - inputValue = 111 + inputValue = 1111 initiationHeight = 550 ) @@ -1418,7 +1418,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { sweepReq := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -1427,7 +1427,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 1000, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -1711,7 +1711,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{2, 2}, Index: 2, @@ -1726,7 +1726,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { // CltvExpiry is not urgent, but close. CltvExpiry: 600 + blocksInDelay*2 + 5, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -1794,7 +1794,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { sweepReq3 := SweepRequest{ SwapHash: lntypes.Hash{3, 3, 3}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{3, 3}, Index: 3, @@ -1807,7 +1807,7 @@ func testDelays(t *testing.T, store testStore, batcherStore testBatcherStore) { // CltvExpiry is urgent. CltvExpiry: 600 + blocksInDelay*2 - 5, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -1870,8 +1870,8 @@ func testCustomDelays(t *testing.T, store testStore, swapHash2 := lntypes.Hash{2, 2, 2} const ( - swapSize1 = 111 - swapSize2 = 222 + swapSize1 = 1111 + swapSize2 = 2222 ) // initialDelay returns initialDelay depending of batch size (sats). @@ -1944,7 +1944,7 @@ func testCustomDelays(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 1000, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -2012,7 +2012,7 @@ func testCustomDelays(t *testing.T, store testStore, swap2 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 1000, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2151,7 +2151,7 @@ func testMaxSweepsPerBatch(t *testing.T, store testStore, sweepReq := SweepRequest{ SwapHash: swapHash, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: outpoint, }}, Notifier: &dummyNotifier, @@ -2160,7 +2160,7 @@ func testMaxSweepsPerBatch(t *testing.T, store testStore, swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 1000, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2268,7 +2268,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, Hash: chainhash.Hash{1, 1}, Index: 1, } - value1 := btcutil.Amount(111) + value1 := btcutil.Amount(1111) sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ @@ -2281,7 +2281,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -2297,7 +2297,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 222, + Value: 2222, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{2, 2}, Index: 2, @@ -2309,7 +2309,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, swap2 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2328,7 +2328,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, sweepReq3 := SweepRequest{ SwapHash: lntypes.Hash{3, 3, 3}, Inputs: []Input{{ - Value: 333, + Value: 3333, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{3, 3}, Index: 3, @@ -2340,7 +2340,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, swap3 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 333, + AmountRequested: 3333, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2535,7 +2535,7 @@ func testSweepBatcherGroup(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -2554,11 +2554,11 @@ func testSweepBatcherGroup(t *testing.T, store testStore, Inputs: []Input{ { Outpoint: outpoint1, - Value: 111, + Value: 1111, }, { Outpoint: outpoint2, - Value: 222, + Value: 2222, }, }, Notifier: &dummyNotifier, @@ -2620,7 +2620,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -2629,7 +2629,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -2667,7 +2667,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 222, + Value: 2222, Outpoint: op2, }}, Notifier: &dummyNotifier, @@ -2676,7 +2676,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, swap2 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2713,7 +2713,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, sweepReq3 := SweepRequest{ SwapHash: lntypes.Hash{3, 3, 3}, Inputs: []Input{{ - Value: 333, + Value: 3333, Outpoint: op3, }}, Notifier: &dummyNotifier, @@ -2722,7 +2722,7 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, swap3 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 1, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2838,7 +2838,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -2847,7 +2847,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, swap1 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -2865,7 +2865,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 222, + Value: 2222, Outpoint: op2, }}, Notifier: &dummyNotifier, @@ -2874,7 +2874,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, swap2 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -2895,7 +2895,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, sweepReq3 := SweepRequest{ SwapHash: lntypes.Hash{3, 3, 3}, Inputs: []Input{{ - Value: 333, + Value: 3333, Outpoint: op3, }}, Notifier: &dummyNotifier, @@ -2904,7 +2904,7 @@ func testSweepBatcherComposite(t *testing.T, store testStore, swap3 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 3, - AmountRequested: 333, + AmountRequested: 3333, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, @@ -3241,7 +3241,7 @@ func testRestoringEmptyBatch(t *testing.T, store testStore, sweepReq := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op, }}, Notifier: &dummyNotifier, @@ -3250,7 +3250,7 @@ func testRestoringEmptyBatch(t *testing.T, store testStore, swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -3425,7 +3425,7 @@ func testHandleSweepTwice(t *testing.T, backend testStore, sweepReq1 := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op1, }}, Notifier: &dummyNotifier, @@ -3438,7 +3438,7 @@ func testHandleSweepTwice(t *testing.T, backend testStore, Contract: &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: shortCltv, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -3455,7 +3455,7 @@ func testHandleSweepTwice(t *testing.T, backend testStore, sweepReq2 := SweepRequest{ SwapHash: lntypes.Hash{2, 2, 2}, Inputs: []Input{{ - Value: 222, + Value: 2222, Outpoint: op2, }}, Notifier: &dummyNotifier, @@ -3468,7 +3468,7 @@ func testHandleSweepTwice(t *testing.T, backend testStore, Contract: &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: longCltv, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -3523,7 +3523,7 @@ func testHandleSweepTwice(t *testing.T, backend testStore, Contract: &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: shortCltv, - AmountRequested: 222, + AmountRequested: 2222, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -3627,7 +3627,7 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, sweepReq := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: op, }}, Notifier: &dummyNotifier, @@ -3636,7 +3636,7 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, @@ -3954,7 +3954,7 @@ func testSweepBatcherCloseDuringAdding(t *testing.T, store testStore, swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{i}, @@ -3980,7 +3980,7 @@ func testSweepBatcherCloseDuringAdding(t *testing.T, store testStore, sweepReq := SweepRequest{ SwapHash: lntypes.Hash{i, i, i}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{i, i}, Index: 1, @@ -4062,7 +4062,7 @@ func testCustomSignMuSig2(t *testing.T, store testStore, sweepReq := SweepRequest{ SwapHash: lntypes.Hash{1, 1, 1}, Inputs: []Input{{ - Value: 111, + Value: 1111, Outpoint: wire.OutPoint{ Hash: chainhash.Hash{1, 1}, Index: 1, @@ -4074,7 +4074,7 @@ func testCustomSignMuSig2(t *testing.T, store testStore, swap := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111, - AmountRequested: 111, + AmountRequested: 1111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, HtlcKeys: htlcKeys, }, From dbed63e13958e9694bf6c6bac154342899fdc87d Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Wed, 11 Jun 2025 14:56:04 +0200 Subject: [PATCH 5/5] swapserverrpc: arbitrary static swap amount --- swapserverrpc/staticaddr.pb.go | 264 ++++++++++++++++++--------------- swapserverrpc/staticaddr.proto | 15 +- 2 files changed, 155 insertions(+), 124 deletions(-) diff --git a/swapserverrpc/staticaddr.pb.go b/swapserverrpc/staticaddr.pb.go index 4f5cd4134..0deaf1252 100644 --- a/swapserverrpc/staticaddr.pb.go +++ b/swapserverrpc/staticaddr.pb.go @@ -398,7 +398,9 @@ type ServerStaticAddressLoopInRequest struct { // The hashed swap invoice preimage of the swap. SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` // The deposit outpoints the client wishes to loop in. They implicitly state - // the swap amount. + // the swap amount if the amount field is not specified. If the amount field + // is specified, the server will use the total amount of the deposit + // outpoints minus the amount as the change amount. DepositOutpoints []string `protobuf:"bytes,3,rep,name=deposit_outpoints,json=depositOutpoints,proto3" json:"deposit_outpoints,omitempty"` // The swap invoice that the client wants the server to pay. SwapInvoice string `protobuf:"bytes,4,opt,name=swap_invoice,json=swapInvoice,proto3" json:"swap_invoice,omitempty"` @@ -424,6 +426,14 @@ type ServerStaticAddressLoopInRequest struct { // swap payment. If the timeout is reached the swap will be aborted on the // server side and the client can retry the swap with different parameters. PaymentTimeoutSeconds uint32 `protobuf:"varint,8,opt,name=payment_timeout_seconds,json=paymentTimeoutSeconds,proto3" json:"payment_timeout_seconds,omitempty"` + // The optional swap amount the client is attempting to swap. If specified the + // server will take out this amount from the total value of provided + // deposit_outpoints and will send the change back to the static address. If + // this results in dust change the server will reject the swap request. If the + // amount is not specified the server will use the total amount of the + // deposit_outpoints as swap amount without providing an additional flag - this + // is to maintain backwards compatibility. + Amount uint64 `protobuf:"varint,9,opt,name=amount,proto3" json:"amount,omitempty"` } func (x *ServerStaticAddressLoopInRequest) Reset() { @@ -514,6 +524,13 @@ func (x *ServerStaticAddressLoopInRequest) GetPaymentTimeoutSeconds() uint32 { return 0 } +func (x *ServerStaticAddressLoopInRequest) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + type ServerStaticAddressLoopInResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1050,7 +1067,7 @@ var file_staticaddr_proto_rawDesc = []byte{ 0x52, 0x0f, 0x6d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x53, 0x77, 0x65, 0x65, 0x70, 0x53, 0x69, 0x67, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x82, 0x03, 0x0a, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x9a, 0x03, 0x0a, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x13, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, @@ -1074,129 +1091,130 @@ var file_staticaddr_proto_rawDesc = []byte{ 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xe1, 0x02, 0x0a, 0x21, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, - 0x68, 0x74, 0x6c, 0x63, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, - 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x68, 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, - 0x79, 0x12, 0x4c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, 0x68, 0x74, - 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, - 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x10, 0x73, - 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x4b, 0x0a, 0x12, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, - 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, - 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, - 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x68, 0x69, 0x67, - 0x68, 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x51, 0x0a, 0x15, - 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, - 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, - 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, - 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x65, 0x78, 0x74, - 0x72, 0x65, 0x6d, 0x65, 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x22, - 0x4a, 0x0a, 0x15, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, - 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x6e, 0x63, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xad, 0x02, 0x0a, 0x20, + 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x22, 0xe1, 0x02, 0x0a, 0x21, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x68, 0x74, 0x6c, 0x63, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x68, + 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x4c, 0x0a, 0x12, 0x73, 0x74, 0x61, + 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, + 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x10, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x48, + 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x12, 0x68, 0x69, 0x67, 0x68, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x68, 0x69, 0x67, 0x68, 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, 0x5f, + 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, 0x46, 0x65, 0x65, 0x48, + 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x4a, 0x0a, 0x15, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, + 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x66, 0x65, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x22, 0xad, 0x02, 0x0a, 0x20, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, + 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, + 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, + 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x10, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x12, 0x68, 0x69, 0x67, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x0f, 0x68, 0x69, 0x67, 0x68, 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x12, 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x49, + 0x6e, 0x66, 0x6f, 0x22, 0x43, 0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x6c, + 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, + 0x6e, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x04, 0x73, 0x69, 0x67, 0x73, 0x22, 0x23, 0x0a, 0x21, 0x50, 0x75, 0x73, 0x68, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, + 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc6, 0x02, + 0x0a, 0x25, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, + 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x62, 0x0a, 0x0c, 0x73, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, + 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, + 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x63, 0x0a, 0x10, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x44, 0x0a, 0x1a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x69, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x73, 0x69, 0x67, 0x22, 0x28, 0x0a, 0x26, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4c, 0x0a, - 0x12, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, - 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x10, 0x73, 0x74, 0x61, 0x6e, 0x64, - 0x61, 0x72, 0x64, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x12, 0x68, - 0x69, 0x67, 0x68, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x68, 0x69, 0x67, 0x68, 0x46, 0x65, 0x65, - 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, 0x74, 0x72, - 0x65, 0x6d, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x65, 0x78, 0x74, 0x72, 0x65, 0x6d, 0x65, - 0x46, 0x65, 0x65, 0x48, 0x74, 0x6c, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x43, 0x0a, 0x15, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x73, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x69, 0x67, 0x73, - 0x22, 0x23, 0x0a, 0x21, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc6, 0x02, 0x0a, 0x25, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, - 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, - 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, - 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, - 0x12, 0x62, 0x0a, 0x0c, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x26, 0x0a, 0x1c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x56, 0x30, 0x10, 0x00, 0x32, 0xb5, + 0x04, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x57, 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x65, 0x77, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x59, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x19, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x12, 0x29, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, + 0x0a, 0x19, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x12, 0x29, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, - 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x63, 0x0a, 0x10, 0x53, 0x69, 0x67, - 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, - 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, - 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x44, - 0x0a, 0x1a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, - 0x73, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, - 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, - 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x03, 0x73, 0x69, 0x67, 0x22, 0x28, 0x0a, 0x26, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, - 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, - 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x26, - 0x0a, 0x1c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x06, - 0x0a, 0x02, 0x56, 0x30, 0x10, 0x00, 0x32, 0xb5, 0x04, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x57, - 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x72, 0x0a, 0x19, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, - 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x12, - 0x29, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, - 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, - 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x19, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, - 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, - 0x69, 0x67, 0x73, 0x12, 0x29, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, - 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, - 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, - 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, - 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, - 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1e, 0x50, - 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x12, 0x2e, 0x2e, - 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, - 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, - 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, - 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, - 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, - 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, - 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, - 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, - 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x73, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x1e, 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, + 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, + 0x73, 0x53, 0x69, 0x67, 0x73, 0x12, 0x2e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x75, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x53, 0x77, 0x65, 0x65, 0x70, 0x6c, 0x65, 0x73, 0x73, 0x53, 0x69, 0x67, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, + 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/swapserverrpc/staticaddr.proto b/swapserverrpc/staticaddr.proto index 9aceefb3b..ce6890a28 100644 --- a/swapserverrpc/staticaddr.proto +++ b/swapserverrpc/staticaddr.proto @@ -107,7 +107,9 @@ message ServerStaticAddressLoopInRequest { bytes swap_hash = 2; // The deposit outpoints the client wishes to loop in. They implicitly state - // the swap amount. + // the swap amount if the amount field is not specified. If the amount field + // is specified, the server will use the total amount of the deposit + // outpoints minus the amount as the change amount. repeated string deposit_outpoints = 3; // The swap invoice that the client wants the server to pay. @@ -135,6 +137,17 @@ message ServerStaticAddressLoopInRequest { // swap payment. If the timeout is reached the swap will be aborted on the // server side and the client can retry the swap with different parameters. uint32 payment_timeout_seconds = 8; + + /* + The optional swap amount the client is attempting to swap. If specified the + server will take out this amount from the total value of provided + deposit_outpoints and will send the change back to the static address. If + this results in dust change the server will reject the swap request. If the + amount is not specified the server will use the total amount of the + deposit_outpoints as swap amount without providing an additional flag - this + is to maintain backwards compatibility. + */ + uint64 amount = 9; } message ServerStaticAddressLoopInResponse {