Skip to content

swapserverrpc: arbitrary static swap amount #951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 141 additions & 123 deletions swapserverrpc/staticaddr.pb.go

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion swapserverrpc/staticaddr.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions sweepbatcher/greedy_batch_selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
88 changes: 82 additions & 6 deletions sweepbatcher/greedy_batch_selection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down
39 changes: 25 additions & 14 deletions sweepbatcher/presigned.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep,
outpoint: s.outpoint,
value: s.value,
presigned: s.presigned,
change: s.change,
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)),
)
Expand Down
Loading