multi: add preimage push to loop out after sweep attempt

Once we have revealed our preimage to the world with a sweep attempt,
we can safely push our preimage to the server to speed up on chain
claim.

Rather than rely on the server, we use the state of our invoice in lnd
to determine whether we should continue trying to push the preimage to
the server.
pull/193/head
carla 4 years ago
parent 488df785e0
commit 65c847674d
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -12,7 +12,9 @@ import (
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
var (
@ -261,6 +263,10 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.AssertRegisterSpendNtfn(confIntent.PkScript)
// Assert that a call to track payment was sent, and respond with status
// in flight so that our swap will push its preimage to the server.
ctx.trackPayment(lnrpc.Payment_IN_FLIGHT)
// Publish tick.
ctx.expiryChan <- testTime
@ -287,6 +293,13 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
ctx.T.Fatalf("incorrect preimage")
}
// Since we successfully published our sweep, we expect the preimage to
// have been pushed to our mock server.
preimage, err := lntypes.MakePreimage(clientPreImage)
require.NoError(ctx.T, err)
ctx.assertPreimagePush(preimage)
// Simulate server pulling payment.
signalSwapPaymentResult(nil)

@ -13,6 +13,7 @@ require (
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200603030653-09bb9db78246
github.com/lightningnetwork/lnd/queue v1.0.4
github.com/stretchr/testify v1.4.0
github.com/urfave/cli v1.20.0
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0
golang.org/x/text v0.3.2 // indirect

@ -1,14 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU=
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
@ -23,14 +19,12 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQe94vza2qkvf7Ns8XX2Rjh/vdIhO3RzGj4o=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
@ -48,7 +42,6 @@ github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48T
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/walletdb v1.3.1 h1:lW1Ac3F1jJY4K11P+YQtRNcP5jFk27ASfrV7C6mvRU0=
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe h1:yQbJVYfsKbdqDQNLxd4hhiLSiMkIygefW5mSHMsdKpc=
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ=
@ -135,9 +128,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.12.2 h1:D0EVSTwQoQOyfY35QNSuPJA4jpZRtk
github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@ -178,7 +169,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
@ -191,7 +181,6 @@ github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200603030653-09bb9db78246/g
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/clock v1.0.0 h1:U9uknV7DwBsNUzc+x11tx1bPvoXiEYcJ9wrIemRibKE=
github.com/lightningnetwork/lnd/clock v1.0.0/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k=
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
@ -256,7 +245,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
@ -280,7 +268,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -320,7 +307,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -373,7 +359,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -706,6 +706,20 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
return nil, fmt.Errorf("register spend ntfn: %v", err)
}
// Track our payment status so that we can detect whether our off chain
// htlc is settled. We track this information to determine whether it is
// necessary to continue trying to push our preimage to the server.
trackChan, trackErrChan, err := s.lnd.Router.TrackPayment(
ctx, s.hash,
)
if err != nil {
return nil, fmt.Errorf("track payment: %v", err)
}
// paymentComplete tracks whether our payment is complete, and is used
// to decide whether we need to push our preimage to the server.
var paymentComplete bool
timerChan := s.timerFactory(republishDelay)
for {
select {
@ -719,6 +733,45 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
case err := <-spendErr:
return nil, err
// Receive status updates for our payment so that we can detect
// whether we've successfully pushed our preimage.
case status, ok := <-trackChan:
// If our channel has been closed, indicating that the
// server is finished providing updates because the
// payment has reached a terminal state, we replace
// the closed channel with nil so that we will no longer
// listen on it.
if !ok {
trackChan = nil
continue
}
if status.State == lnrpc.Payment_SUCCEEDED {
s.log.Infof("Off chain payment succeeded")
paymentComplete = true
}
// If we receive a track payment error that indicates that the
// server stream is complete, we ignore it because we want to
// continue this loop beyond the completion of the payment.
case err, ok := <-trackErrChan:
// If our channel has been closed, indicating that the
// server is finished providing updates because the
// payment has reached a terminal state, we replace
// the closed channel with nil so that we will no longer
// listen on it.
if !ok {
trackErrChan = nil
continue
}
// Otherwise, if we receive a non-nil error, we return
// it.
if err != nil {
return nil, err
}
// New block arrived, update height and restart the republish
// timer.
case notification := <-s.blockEpochChan:
@ -733,6 +786,12 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
return nil, err
}
// If our off chain payment is not yet complete, we
// try to push our preimage to the server.
if !paymentComplete {
s.pushPreimage(ctx)
}
// Context canceled.
case <-globalCtx.Done():
return nil, globalCtx.Err()
@ -740,6 +799,26 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
}
}
// pushPreimage pushes our preimage to the server if we have already revealed
// our preimage on chain with a sweep attempt.
func (s *loopOutSwap) pushPreimage(ctx context.Context) {
// If we have not yet revealed our preimage through a sweep, we do not
// push the preimage because we may choose to never sweep if fees are
// too high.
if s.state != loopdb.StatePreimageRevealed {
return
}
s.log.Infof("Pushing preimage to server")
// Push the preimage to the server, just log server errors since we rely
// on our payment state rather than the server response to judge the
// outcome of our preimage push.
if err := s.server.PushLoopOutPreimage(ctx, s.Preimage); err != nil {
s.log.Warnf("Could not push preimage: %v", err)
}
}
// sweep tries to sweep the given htlc to a destination address. It takes into
// account the max miner fee and marks the preimage as revealed when it
// published the tx.

@ -14,6 +14,9 @@ import (
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/stretchr/testify/require"
)
// TestLoopOutPaymentParameters tests the first part of the loop out process up
@ -216,6 +219,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
lnd := test.NewMockLnd()
ctx := test.NewContext(t, lnd)
server := newServerMock()
// Use the highest sweep confirmation target before we attempt to use
// the default.
@ -228,7 +232,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 10000)
cfg := newSwapConfig(
&lnd.LndServices, newStoreMock(t), newServerMock(),
&lnd.LndServices, newStoreMock(t), server,
)
swap, err := newLoopOutSwap(
@ -295,6 +299,10 @@ func TestCustomSweepConfTarget(t *testing.T) {
// to sweep it using the custom confirmation target.
ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript)
// Assert that we made a query to track our payment, as required for
// preimage push tracking.
trackPayment := ctx.AssertTrackPayment()
expiryChan <- time.Now()
// Expect a signing request for the HTLC success transaction.
@ -348,6 +356,19 @@ func TestCustomSweepConfTarget(t *testing.T) {
// confirmation target.
_ = assertSweepTx(testRequest.SweepConfTarget)
// Once we have published an on chain sweep, we expect a preimage to
// have been pushed to our server.
preimage := <-server.preimagePush
require.Equal(t, swap.Preimage, preimage)
// Now that we have pushed our preimage to the sever, we send an update
// indicating that our off chain htlc is settled. We do this so that
// we don't have to keep consuming preimage pushes from our server mock
// for every sweep attempt.
trackPayment.Updates <- lndclient.PaymentStatus{
State: lnrpc.Payment_SUCCEEDED,
}
// We'll then notify the height at which we begin using the default
// confirmation target.
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
@ -376,3 +397,189 @@ func TestCustomSweepConfTarget(t *testing.T) {
t.Fatal(err)
}
}
// TestPreimagePush tests or logic that decides whether to push our preimage to
// the server. First, we test the case where we have not yet disclosed our
// preimage with a sweep, so we do not want to push our preimage yet. Next, we
// broadcast a sweep attempt and push our preimage to the server. In this stage
// we mock a server failure by not sending a settle update for our payment.
// Finally, we make a last sweep attempt, push the preimage (because we have
// not detected our settle) and settle the off chain htlc, indicating that the
// server successfully settled using the preimage push. In this test, we need
// to start with a fee rate that will be too high, then progress to an
// acceptable one. We do this by starting with a high confirmation target with
// a high fee, and setting the default confirmation fee (which our swap will
// drop down to if it is not confirming in time) to a lower fee. This is not
// intuitive (lower confs having lower fees), but it allows up to mock fee
// changes.
func TestPreimagePush(t *testing.T) {
defer test.Guard(t)()
lnd := test.NewMockLnd()
ctx := test.NewContext(t, lnd)
server := newServerMock()
// Start with a high confirmation delta which will have a very high fee
// attached to it.
testRequest.SweepConfTarget = testLoopOutOnChainCltvDelta -
DefaultSweepConfTargetDelta - 1
// We set our mock fee estimate for our target sweep confs to be our
// max miner fee *2, so that our fee will definitely be above what we
// are willing to pay, and we will not sweep.
ctx.Lnd.SetFeeEstimate(
testRequest.SweepConfTarget, chainfee.SatPerKWeight(
testRequest.MaxMinerFee*2,
),
)
// We set the fee estimate for our default confirmation target very
// low, so that once we drop down to our default confs we will start
// trying to sweep the preimage.
ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 1)
cfg := newSwapConfig(
&lnd.LndServices, newStoreMock(t), server,
)
swap, err := newLoopOutSwap(
context.Background(), cfg, ctx.Lnd.Height, testRequest,
)
require.NoError(t, err)
// Set up the required dependencies to execute the swap.
sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
blockEpochChan := make(chan interface{})
statusChan := make(chan SwapInfo)
expiryChan := make(chan time.Time)
timerFactory := func(_ time.Duration) <-chan time.Time {
return expiryChan
}
errChan := make(chan error)
go func() {
err := swap.execute(context.Background(), &executeConfig{
statusChan: statusChan,
blockEpochChan: blockEpochChan,
timerFactory: timerFactory,
sweeper: sweeper,
}, ctx.Lnd.Height)
if err != nil {
log.Error(err)
}
errChan <- err
}()
// The swap should be found in its initial state.
cfg.store.(*storeMock).assertLoopOutStored()
state := <-statusChan
require.Equal(t, loopdb.StateInitiated, state.State)
// We'll then pay both the swap and prepay invoice, which should trigger
// the server to publish the on-chain HTLC.
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
signalSwapPaymentResult(nil)
signalPrepaymentResult(nil)
// Notify the confirmation notification for the HTLC.
ctx.AssertRegisterConf()
blockEpochChan <- ctx.Lnd.Height + 1
htlcTx := wire.NewMsgTx(2)
htlcTx.AddTxOut(&wire.TxOut{
Value: int64(swap.AmountRequested),
PkScript: swap.htlc.PkScript,
})
ctx.NotifyConf(htlcTx)
// The client should then register for a spend of the HTLC and attempt
// to sweep it using the custom confirmation target.
ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript)
// Assert that we made a query to track our payment, as required for
// preimage push tracking.
trackPayment := ctx.AssertTrackPayment()
// Tick the expiry channel, we are still using our client confirmation
// target at this stage which has fees higher than our max acceptable
// fee. We do not expect a sweep attempt at this point. Since our
// preimage is not revealed, we also do not expect a preimage push.
expiryChan <- testTime
// Now, we notify the height at which the client will start using the
// default confirmation target. This has the effect of lowering our fees
// so that the client still start sweeping.
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
DefaultSweepConfTargetDelta
blockEpochChan <- defaultConfTargetHeight
// This time when we tick the expiry chan, our fees are lower than the
// swap max, so we expect it to prompt a sweep.
expiryChan <- testTime
// Expect a signing request for the HTLC success transaction.
<-ctx.Lnd.SignOutputRawChannel
// This is the first time we have swept, so we expect our preimage
// revealed state to be set.
cfg.store.(*storeMock).assertLoopOutState(loopdb.StatePreimageRevealed)
status := <-statusChan
require.Equal(
t, status.State, loopdb.SwapState(loopdb.StatePreimageRevealed),
)
// We expect the sweep tx to have been published.
ctx.ReceiveTx()
// Once we have published an on chain sweep, we expect a preimage to
// have been pushed to the server after the sweep.
preimage := <-server.preimagePush
require.Equal(t, swap.Preimage, preimage)
// To mock a server failure, we do not send a payment settled update
// for our off chain payment yet. We also do not confirm our sweep on
// chain yet so we can test our preimage push retry logic. Instead, we
// tick the expiry chan again to prompt another sweep.
expiryChan <- testTime
// We expect another signing request for out sweep, and publish of our
// sweep transaction.
<-ctx.Lnd.SignOutputRawChannel
ctx.ReceiveTx()
// Since we have not yet been notified of an off chain settle, and we
// have attempted to sweep again, we expect another preimage push
// attempt.
preimage = <-server.preimagePush
require.Equal(t, swap.Preimage, preimage)
// This time, we send a payment succeeded update into our payment stream
// to reflect that the server received our preimage push and settled off
// chain.
trackPayment.Updates <- lndclient.PaymentStatus{
State: lnrpc.Payment_SUCCEEDED,
}
// We tick one last time, this time expecting a sweep but no preimage
// push. The test's mocked preimage channel is un-buffered, so our test
// would hang if we pushed the preimage here.
expiryChan <- testTime
<-ctx.Lnd.SignOutputRawChannel
sweepTx := ctx.ReceiveTx()
// Finally, we put this swap out of its misery and notify a successful
// spend our our sweepTx and assert that the swap succeeds.
ctx.NotifySpend(sweepTx, 0)
cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
status = <-statusChan
require.Equal(
t, status.State, loopdb.SwapState(loopdb.StateSuccess),
)
require.NoError(t, <-errChan)
}

@ -39,18 +39,26 @@ const (
//Loop will use native segwit (P2WSH) htlcs by default, while externally
//published htlcs may use native (P2WSH) or nested (NP2WSH) segwit as well.
ProtocolVersion_NATIVE_SEGWIT_LOOP_IN ProtocolVersion = 2
//
//Once the on chain loop out htlc is confirmed, the client can push the swap
//preimage to the server to speed up claim of their off chain htlc (acquiring
//incoming liquidity more quickly than if the server waited for the on chain
//claim tx).
ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT ProtocolVersion = 3
)
var ProtocolVersion_name = map[int32]string{
0: "LEGACY",
1: "MULTI_LOOP_OUT",
2: "NATIVE_SEGWIT_LOOP_IN",
3: "PREIMAGE_PUSH_LOOP_OUT",
}
var ProtocolVersion_value = map[string]int32{
"LEGACY": 0,
"MULTI_LOOP_OUT": 1,
"NATIVE_SEGWIT_LOOP_IN": 2,
"LEGACY": 0,
"MULTI_LOOP_OUT": 1,
"NATIVE_SEGWIT_LOOP_IN": 2,
"PREIMAGE_PUSH_LOOP_OUT": 3,
}
func (x ProtocolVersion) String() string {
@ -771,6 +779,91 @@ func (m *ServerLoopInTerms) GetMaxSwapAmount() uint64 {
return 0
}
// ServerLoopOutPushPreimageRequest pushes a preimage to the server. Note that
// this call returns with no error after the server acknowledges the preimage
// and does not block until the invoice is settled.
type ServerLoopOutPushPreimageRequest struct {
// The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
//
//Preimage is the preimage of the loop out swap that we wish to push to the
//server to speed up off-chain claim once the on-chain htlc has confirmed.
Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopOutPushPreimageRequest) Reset() { *m = ServerLoopOutPushPreimageRequest{} }
func (m *ServerLoopOutPushPreimageRequest) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutPushPreimageRequest) ProtoMessage() {}
func (*ServerLoopOutPushPreimageRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{12}
}
func (m *ServerLoopOutPushPreimageRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutPushPreimageRequest.Unmarshal(m, b)
}
func (m *ServerLoopOutPushPreimageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopOutPushPreimageRequest.Marshal(b, m, deterministic)
}
func (m *ServerLoopOutPushPreimageRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopOutPushPreimageRequest.Merge(m, src)
}
func (m *ServerLoopOutPushPreimageRequest) XXX_Size() int {
return xxx_messageInfo_ServerLoopOutPushPreimageRequest.Size(m)
}
func (m *ServerLoopOutPushPreimageRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopOutPushPreimageRequest.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopOutPushPreimageRequest proto.InternalMessageInfo
func (m *ServerLoopOutPushPreimageRequest) GetProtocolVersion() ProtocolVersion {
if m != nil {
return m.ProtocolVersion
}
return ProtocolVersion_LEGACY
}
func (m *ServerLoopOutPushPreimageRequest) GetPreimage() []byte {
if m != nil {
return m.Preimage
}
return nil
}
type ServerLoopOutPushPreimageResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ServerLoopOutPushPreimageResponse) Reset() { *m = ServerLoopOutPushPreimageResponse{} }
func (m *ServerLoopOutPushPreimageResponse) String() string { return proto.CompactTextString(m) }
func (*ServerLoopOutPushPreimageResponse) ProtoMessage() {}
func (*ServerLoopOutPushPreimageResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{13}
}
func (m *ServerLoopOutPushPreimageResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ServerLoopOutPushPreimageResponse.Unmarshal(m, b)
}
func (m *ServerLoopOutPushPreimageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ServerLoopOutPushPreimageResponse.Marshal(b, m, deterministic)
}
func (m *ServerLoopOutPushPreimageResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ServerLoopOutPushPreimageResponse.Merge(m, src)
}
func (m *ServerLoopOutPushPreimageResponse) XXX_Size() int {
return xxx_messageInfo_ServerLoopOutPushPreimageResponse.Size(m)
}
func (m *ServerLoopOutPushPreimageResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ServerLoopOutPushPreimageResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ServerLoopOutPushPreimageResponse proto.InternalMessageInfo
func init() {
proto.RegisterEnum("looprpc.ProtocolVersion", ProtocolVersion_name, ProtocolVersion_value)
proto.RegisterType((*ServerLoopOutRequest)(nil), "looprpc.ServerLoopOutRequest")
@ -785,63 +878,70 @@ func init() {
proto.RegisterType((*ServerLoopInQuoteResponse)(nil), "looprpc.ServerLoopInQuoteResponse")
proto.RegisterType((*ServerLoopInTermsRequest)(nil), "looprpc.ServerLoopInTermsRequest")
proto.RegisterType((*ServerLoopInTerms)(nil), "looprpc.ServerLoopInTerms")
proto.RegisterType((*ServerLoopOutPushPreimageRequest)(nil), "looprpc.ServerLoopOutPushPreimageRequest")
proto.RegisterType((*ServerLoopOutPushPreimageResponse)(nil), "looprpc.ServerLoopOutPushPreimageResponse")
}
func init() { proto.RegisterFile("server.proto", fileDescriptor_ad098daeda4239f7) }
var fileDescriptor_ad098daeda4239f7 = []byte{
// 810 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdd, 0x52, 0xe2, 0x48,
0x14, 0xde, 0x04, 0x04, 0x39, 0xf2, 0x67, 0xef, 0xba, 0x1b, 0x50, 0xb6, 0x90, 0xaa, 0xb5, 0x28,
0x2f, 0xb4, 0xca, 0xbd, 0xdb, 0x3b, 0xd6, 0x5f, 0x6a, 0x59, 0xd1, 0x88, 0x6e, 0xed, 0x55, 0xa6,
0x85, 0x1e, 0x49, 0x4d, 0x48, 0xc7, 0xa4, 0x41, 0x79, 0x93, 0x99, 0x97, 0x98, 0xb7, 0x99, 0x57,
0x98, 0xaa, 0xb9, 0x9c, 0x37, 0x98, 0xea, 0xee, 0x44, 0x92, 0x10, 0x54, 0xaa, 0xbc, 0x33, 0xe7,
0x7c, 0x76, 0x7f, 0xdf, 0x77, 0xce, 0xd7, 0x40, 0xde, 0x23, 0xee, 0x84, 0xb8, 0x7b, 0x8e, 0x4b,
0x19, 0x45, 0x59, 0x8b, 0x52, 0xc7, 0x75, 0xfa, 0xd5, 0xad, 0x3b, 0x4a, 0xef, 0x2c, 0xb2, 0x8f,
0x1d, 0x73, 0x1f, 0xdb, 0x36, 0x65, 0x98, 0x99, 0xd4, 0xf6, 0x24, 0xac, 0xf1, 0x4d, 0x81, 0x5f,
0xae, 0xc4, 0xff, 0x75, 0x28, 0x75, 0xba, 0x63, 0xa6, 0x93, 0xfb, 0x31, 0xf1, 0x18, 0xda, 0x86,
0xbc, 0x4b, 0xfa, 0xc4, 0x9c, 0x10, 0xd7, 0xf8, 0x40, 0xa6, 0x9a, 0x52, 0x57, 0x9a, 0x79, 0x7d,
0x2d, 0xa8, 0xfd, 0x43, 0xa6, 0x68, 0x13, 0x72, 0xde, 0x03, 0x76, 0x8c, 0x21, 0xf6, 0x86, 0x9a,
0x2a, 0xfa, 0xab, 0xbc, 0x70, 0x86, 0xbd, 0x21, 0x2a, 0x43, 0x0a, 0x8f, 0x98, 0x96, 0xaa, 0x2b,
0xcd, 0xb4, 0xce, 0xff, 0x44, 0x7f, 0x41, 0x45, 0xc0, 0x9d, 0xf1, 0xad, 0x65, 0xf6, 0x05, 0x0b,
0x63, 0x40, 0xf0, 0xc0, 0x32, 0x6d, 0xa2, 0xa5, 0xeb, 0x4a, 0x33, 0xa5, 0xff, 0xc6, 0x01, 0x17,
0xb3, 0xfe, 0x91, 0xdf, 0x46, 0x87, 0x50, 0x16, 0x7c, 0xfb, 0xd4, 0x32, 0x26, 0xc4, 0xf5, 0x4c,
0x6a, 0x6b, 0x2b, 0x75, 0xa5, 0x59, 0x3c, 0xd0, 0xf6, 0x7c, 0xa1, 0x7b, 0x17, 0x3e, 0xe0, 0x46,
0xf6, 0xf5, 0x92, 0x13, 0x2d, 0x34, 0x3e, 0x2a, 0xb0, 0x11, 0xd3, 0xea, 0x39, 0xd4, 0xf6, 0x08,
0x17, 0x2b, 0xa8, 0x99, 0xf6, 0x84, 0x9a, 0x7d, 0x22, 0xc4, 0xe6, 0xf4, 0x35, 0x5e, 0x6b, 0xcb,
0x12, 0xfa, 0x03, 0x8a, 0x8e, 0x4b, 0x1c, 0x3c, 0x7d, 0x02, 0xa9, 0x02, 0x54, 0x90, 0xd5, 0x00,
0x56, 0x03, 0xf0, 0x88, 0x3d, 0xf0, 0x4d, 0x4b, 0x09, 0x53, 0x72, 0xb2, 0xc2, 0x2d, 0xfb, 0x15,
0x32, 0xe4, 0xd1, 0x31, 0xdd, 0xa9, 0x10, 0xbc, 0xa2, 0xfb, 0x5f, 0x8d, 0xcf, 0x0a, 0x54, 0x22,
0xd4, 0x2e, 0xc7, 0x94, 0x91, 0x60, 0x16, 0xbe, 0x97, 0xca, 0x2b, 0xbd, 0x54, 0x97, 0xf7, 0x32,
0xb5, 0xac, 0x97, 0x9f, 0x54, 0x40, 0xf3, 0x84, 0xd1, 0x2e, 0xac, 0x4b, 0x5e, 0x78, 0x3a, 0x22,
0x36, 0x33, 0x06, 0xc4, 0x63, 0xbe, 0x9b, 0x25, 0xc1, 0x47, 0xd6, 0x8f, 0xb8, 0xaa, 0x0a, 0x88,
0x6d, 0x31, 0xde, 0x93, 0x80, 0x72, 0x96, 0x7f, 0x9f, 0x10, 0x82, 0x76, 0xa0, 0x10, 0xb4, 0x0c,
0x17, 0x33, 0x22, 0xf8, 0xa5, 0xfe, 0x56, 0x35, 0x45, 0x0e, 0xe5, 0x84, 0x10, 0x1d, 0x33, 0xe1,
0xb6, 0x3f, 0x14, 0xee, 0x4f, 0x5a, 0xf8, 0x93, 0x93, 0x95, 0xd6, 0x88, 0xa1, 0x5d, 0x28, 0x8d,
0x4c, 0xdb, 0x10, 0x47, 0xe1, 0x11, 0x1d, 0xdb, 0x4c, 0x2c, 0x4d, 0x5a, 0x1c, 0x54, 0x18, 0x99,
0xf6, 0xd5, 0x03, 0x76, 0x5a, 0xa2, 0x21, 0xb0, 0xf8, 0x31, 0x82, 0xcd, 0x84, 0xb0, 0xf8, 0x31,
0x84, 0xad, 0x01, 0xf4, 0x2d, 0x36, 0x31, 0x06, 0xc4, 0x62, 0x58, 0xcb, 0x8a, 0x49, 0xe6, 0x78,
0xe5, 0x88, 0x17, 0x1a, 0xef, 0x62, 0xb3, 0xec, 0x11, 0x77, 0xe4, 0x05, 0xb3, 0x4c, 0x72, 0x5f,
0x59, 0xd6, 0xfd, 0x41, 0xcc, 0x7c, 0x71, 0x03, 0xda, 0x99, 0x97, 0x2b, 0x57, 0x26, 0x26, 0x75,
0x67, 0x5e, 0xaa, 0xea, 0xe3, 0xc2, 0x32, 0x1b, 0x5f, 0x15, 0xf8, 0x79, 0x76, 0x4d, 0xdb, 0x0e,
0x24, 0x44, 0x77, 0x5c, 0x89, 0xef, 0xf8, 0x92, 0xcf, 0x42, 0x3c, 0x7b, 0xe9, 0xf9, 0xec, 0x55,
0x60, 0xd5, 0xc2, 0x1e, 0x33, 0x86, 0xd4, 0x11, 0x03, 0xcc, 0xeb, 0x59, 0xfe, 0x7d, 0x46, 0x9d,
0x44, 0x3b, 0x33, 0xcb, 0xda, 0x79, 0x19, 0x7e, 0x03, 0xb9, 0xce, 0xd9, 0xb3, 0xf0, 0xd2, 0x1b,
0x38, 0x0b, 0xb4, 0x1a, 0x09, 0xf4, 0x3d, 0x68, 0xe1, 0x23, 0x5f, 0x88, 0x73, 0x92, 0x0a, 0x75,
0x59, 0x15, 0x5f, 0x22, 0x6f, 0xc8, 0xd3, 0x9d, 0xbe, 0x96, 0x70, 0xda, 0x94, 0x17, 0xd2, 0xa6,
0x26, 0xa7, 0x2d, 0x21, 0x4e, 0xe9, 0x25, 0xe2, 0xb4, 0xf2, 0xba, 0x38, 0x65, 0xe2, 0x71, 0x32,
0xa2, 0x56, 0xbe, 0x7d, 0x9a, 0xfa, 0xb0, 0x3e, 0x77, 0xc1, 0x5b, 0x87, 0x69, 0xb7, 0x03, 0xa5,
0x18, 0x11, 0x04, 0x90, 0xe9, 0x1c, 0x9f, 0xb6, 0x0e, 0xff, 0x2f, 0xff, 0x84, 0x10, 0x14, 0xff,
0xbd, 0xee, 0xf4, 0xda, 0x46, 0xa7, 0xdb, 0xbd, 0x30, 0xba, 0xd7, 0xbd, 0xb2, 0x82, 0x2a, 0xb0,
0x71, 0xde, 0xea, 0xb5, 0x6f, 0x8e, 0x8d, 0xab, 0xe3, 0xd3, 0xff, 0xda, 0x3d, 0xd9, 0x6b, 0x9f,
0x97, 0xd5, 0x83, 0xef, 0x29, 0x00, 0x7e, 0xb8, 0xe4, 0x8d, 0xba, 0x90, 0x8f, 0xbc, 0x04, 0x8d,
0x27, 0xf1, 0x0b, 0x1f, 0xa2, 0xea, 0xe6, 0x33, 0x18, 0xd4, 0x85, 0xe2, 0x39, 0x79, 0xf0, 0x4b,
0xfc, 0x22, 0x54, 0x4b, 0x86, 0x07, 0xa7, 0xfd, 0xbe, 0xa8, 0xed, 0xaf, 0xdf, 0x8c, 0xa1, 0xfc,
0xa1, 0x58, 0xc0, 0x30, 0x9c, 0x93, 0x45, 0x0c, 0xe5, 0x01, 0x1d, 0x58, 0x0b, 0x8f, 0x6b, 0x3b,
0x01, 0x1b, 0xdd, 0x95, 0x6a, 0x75, 0x31, 0x04, 0x75, 0xa0, 0xe0, 0xeb, 0x6d, 0x8b, 0xe1, 0xa2,
0xad, 0x44, 0x70, 0x70, 0x54, 0x6d, 0x41, 0xd7, 0x17, 0xdb, 0x0b, 0xb8, 0x49, 0xaa, 0xc9, 0xdc,
0x22, 0x52, 0x1b, 0xcf, 0x41, 0xe4, 0xa9, 0xb7, 0x19, 0xb1, 0xb7, 0x7f, 0xfe, 0x08, 0x00, 0x00,
0xff, 0xff, 0xd3, 0x5d, 0x9c, 0x31, 0xe8, 0x09, 0x00, 0x00,
// 884 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x41, 0x73, 0xda, 0x46,
0x14, 0xae, 0x04, 0xc6, 0xe6, 0x19, 0xdb, 0x64, 0xd3, 0xa4, 0x82, 0xc4, 0x1d, 0x4c, 0xa7, 0x1e,
0xd7, 0x07, 0x67, 0x26, 0xbd, 0xf5, 0x46, 0x63, 0x62, 0x33, 0x25, 0x86, 0x08, 0x9c, 0x4e, 0x4f,
0xea, 0x06, 0x5e, 0x8d, 0xa6, 0x42, 0xbb, 0x91, 0x16, 0x6c, 0xce, 0xfd, 0x13, 0xed, 0x9f, 0xe8,
0xbf, 0xe9, 0xb9, 0xb7, 0xce, 0xf4, 0x5f, 0x64, 0xb4, 0xbb, 0x32, 0x48, 0x08, 0xdb, 0xcc, 0xf8,
0xc6, 0xbe, 0xf7, 0xe9, 0xed, 0xfb, 0xbe, 0xb7, 0xdf, 0x03, 0x4a, 0x21, 0x06, 0x53, 0x0c, 0x4e,
0x78, 0xc0, 0x04, 0x23, 0x9b, 0x1e, 0x63, 0x3c, 0xe0, 0x83, 0xea, 0xcb, 0x2b, 0xc6, 0xae, 0x3c,
0x7c, 0x45, 0xb9, 0xfb, 0x8a, 0xfa, 0x3e, 0x13, 0x54, 0xb8, 0xcc, 0x0f, 0x15, 0xac, 0xfe, 0xbf,
0x01, 0x5f, 0xf6, 0xe4, 0x77, 0x6d, 0xc6, 0x78, 0x67, 0x22, 0x6c, 0xfc, 0x34, 0xc1, 0x50, 0x90,
0x03, 0x28, 0x05, 0x38, 0x40, 0x77, 0x8a, 0x81, 0xf3, 0x3b, 0xce, 0x2c, 0xa3, 0x66, 0x1c, 0x95,
0xec, 0xed, 0x38, 0xf6, 0x13, 0xce, 0xc8, 0x0b, 0x28, 0x86, 0xd7, 0x94, 0x3b, 0x23, 0x1a, 0x8e,
0x2c, 0x53, 0xe6, 0xb7, 0xa2, 0xc0, 0x39, 0x0d, 0x47, 0xa4, 0x0c, 0x39, 0x3a, 0x16, 0x56, 0xae,
0x66, 0x1c, 0xe5, 0xed, 0xe8, 0x27, 0xf9, 0x01, 0x2a, 0x12, 0xce, 0x27, 0x1f, 0x3d, 0x77, 0x20,
0xbb, 0x70, 0x86, 0x48, 0x87, 0x9e, 0xeb, 0xa3, 0x95, 0xaf, 0x19, 0x47, 0x39, 0xfb, 0xab, 0x08,
0xd0, 0x9d, 0xe7, 0x4f, 0x75, 0x9a, 0xbc, 0x81, 0xb2, 0xec, 0x77, 0xc0, 0x3c, 0x67, 0x8a, 0x41,
0xe8, 0x32, 0xdf, 0xda, 0xa8, 0x19, 0x47, 0xbb, 0xaf, 0xad, 0x13, 0x4d, 0xf4, 0xa4, 0xab, 0x01,
0x1f, 0x54, 0xde, 0xde, 0xe3, 0xc9, 0x40, 0xfd, 0x4f, 0x03, 0x9e, 0xa5, 0xb8, 0x86, 0x9c, 0xf9,
0x21, 0x46, 0x64, 0x65, 0x6b, 0xae, 0x3f, 0x65, 0xee, 0x00, 0x25, 0xd9, 0xa2, 0xbd, 0x1d, 0xc5,
0x5a, 0x2a, 0x44, 0xbe, 0x85, 0x5d, 0x1e, 0x20, 0xa7, 0xb3, 0x5b, 0x90, 0x29, 0x41, 0x3b, 0x2a,
0x1a, 0xc3, 0xf6, 0x01, 0x42, 0xf4, 0x87, 0x5a, 0xb4, 0x9c, 0x14, 0xa5, 0xa8, 0x22, 0x91, 0x64,
0xcf, 0xa1, 0x80, 0x37, 0xdc, 0x0d, 0x66, 0x92, 0xf0, 0x86, 0xad, 0x4f, 0xf5, 0xbf, 0x0d, 0xa8,
0x24, 0x5a, 0x7b, 0x3f, 0x61, 0x02, 0xe3, 0x59, 0x68, 0x2d, 0x8d, 0x07, 0x6a, 0x69, 0xae, 0xaf,
0x65, 0x6e, 0x5d, 0x2d, 0xff, 0x32, 0x81, 0x2c, 0x37, 0x4c, 0x8e, 0xe1, 0x89, 0xea, 0x8b, 0xce,
0xc6, 0xe8, 0x0b, 0x67, 0x88, 0xa1, 0xd0, 0x6a, 0xee, 0xc9, 0x7e, 0x54, 0xfc, 0x34, 0x62, 0x55,
0x01, 0xf9, 0x5a, 0x9c, 0xdf, 0x30, 0x6e, 0x79, 0x33, 0x3a, 0xbf, 0x45, 0x24, 0x87, 0xb0, 0x13,
0xa7, 0x9c, 0x80, 0x0a, 0x94, 0xfd, 0xe5, 0x7e, 0x34, 0x2d, 0x43, 0x0d, 0xe5, 0x2d, 0xa2, 0x4d,
0x85, 0x54, 0x5b, 0x0f, 0x25, 0xd2, 0x27, 0x2f, 0xf5, 0x29, 0xaa, 0x48, 0x63, 0x2c, 0xc8, 0x31,
0xec, 0x8d, 0x5d, 0xdf, 0x91, 0xa5, 0xe8, 0x98, 0x4d, 0x7c, 0x21, 0x1f, 0x4d, 0x5e, 0x16, 0xda,
0x19, 0xbb, 0x7e, 0xef, 0x9a, 0xf2, 0x86, 0x4c, 0x48, 0x2c, 0xbd, 0x49, 0x60, 0x0b, 0x0b, 0x58,
0x7a, 0xb3, 0x80, 0xdd, 0x07, 0x18, 0x78, 0x62, 0xea, 0x0c, 0xd1, 0x13, 0xd4, 0xda, 0x94, 0x93,
0x2c, 0x46, 0x91, 0xd3, 0x28, 0x50, 0xff, 0x35, 0x35, 0xcb, 0x3e, 0x06, 0xe3, 0x30, 0x9e, 0x65,
0x96, 0xfa, 0xc6, 0xba, 0xea, 0x0f, 0x53, 0xe2, 0xcb, 0x1b, 0xc8, 0xe1, 0x32, 0x5d, 0xf5, 0x64,
0x52, 0x54, 0x0f, 0x97, 0xa9, 0x9a, 0x1a, 0xb7, 0x48, 0xb3, 0xfe, 0x9f, 0x01, 0x4f, 0xe7, 0xd7,
0xb4, 0xfc, 0x98, 0x42, 0xf2, 0x8d, 0x1b, 0xe9, 0x37, 0xbe, 0xe6, 0x5a, 0x48, 0x7b, 0x2f, 0xbf,
0xec, 0xbd, 0x0a, 0x6c, 0x79, 0x34, 0x14, 0xce, 0x88, 0x71, 0x39, 0xc0, 0x92, 0xbd, 0x19, 0x9d,
0xcf, 0x19, 0xcf, 0x94, 0xb3, 0xb0, 0xae, 0x9c, 0xef, 0x17, 0x77, 0x60, 0xc4, 0x73, 0xbe, 0x16,
0xee, 0xdb, 0x81, 0x73, 0x43, 0x9b, 0x09, 0x43, 0x7f, 0x02, 0x6b, 0xb1, 0xe4, 0x3d, 0x76, 0xce,
0x62, 0x61, 0xae, 0xcb, 0xe2, 0x9f, 0xc4, 0x0e, 0xb9, 0xbd, 0x53, 0x73, 0x59, 0x74, 0x9b, 0x71,
0x8f, 0xdb, 0xcc, 0x6c, 0xb7, 0x65, 0xd8, 0x29, 0xbf, 0x86, 0x9d, 0x36, 0x1e, 0x66, 0xa7, 0x42,
0xda, 0x4e, 0x4e, 0x52, 0xca, 0xc7, 0x77, 0xd3, 0x00, 0x9e, 0x2c, 0x5d, 0xf0, 0xe8, 0x66, 0xfa,
0xc3, 0x80, 0x5a, 0xc2, 0xb3, 0xdd, 0x49, 0x38, 0xea, 0x06, 0xe8, 0x8e, 0xe9, 0x15, 0x3e, 0x26,
0x1d, 0x52, 0x85, 0x2d, 0xae, 0xeb, 0xc6, 0xf6, 0x8b, 0xcf, 0xf5, 0x6f, 0xe0, 0xe0, 0x8e, 0x26,
0xd4, 0x53, 0x39, 0x1e, 0xc1, 0x5e, 0xea, 0x12, 0x02, 0x50, 0x68, 0x37, 0xcf, 0x1a, 0x6f, 0x7e,
0x29, 0x7f, 0x41, 0x08, 0xec, 0xbe, 0xbb, 0x6c, 0xf7, 0x5b, 0x4e, 0xbb, 0xd3, 0xe9, 0x3a, 0x9d,
0xcb, 0x7e, 0xd9, 0x20, 0x15, 0x78, 0x76, 0xd1, 0xe8, 0xb7, 0x3e, 0x34, 0x9d, 0x5e, 0xf3, 0xec,
0xe7, 0x56, 0x5f, 0xe5, 0x5a, 0x17, 0x65, 0x93, 0x54, 0xe1, 0x79, 0xd7, 0x6e, 0xb6, 0xde, 0x35,
0xce, 0x9a, 0x4e, 0xf7, 0xb2, 0x77, 0x3e, 0xff, 0x2c, 0xf7, 0xfa, 0xdf, 0x3c, 0x40, 0xa4, 0x91,
0xea, 0x89, 0x74, 0xa0, 0x94, 0x58, 0x68, 0xf5, 0x5b, 0xd2, 0x2b, 0xf7, 0x69, 0xf5, 0xc5, 0x1d,
0x18, 0xd2, 0x81, 0xdd, 0x0b, 0xbc, 0xd6, 0xa1, 0xe8, 0x22, 0xb2, 0x9f, 0x0d, 0x8f, 0xab, 0x7d,
0xbd, 0x2a, 0xad, 0x5d, 0xe4, 0xc1, 0xd3, 0x0c, 0xe5, 0xc8, 0x77, 0xd9, 0x9f, 0x65, 0x8c, 0xb8,
0x7a, 0xfc, 0x10, 0xa8, 0xbe, 0x6d, 0xae, 0x87, 0xfa, 0x77, 0x5d, 0xa1, 0xc7, 0xe2, 0x72, 0x59,
0xa5, 0x87, 0x2a, 0xd0, 0x86, 0xed, 0xc5, 0x37, 0x7e, 0x90, 0x81, 0x4d, 0x1a, 0xac, 0x5a, 0x5d,
0x0d, 0x21, 0x6d, 0xd8, 0xd1, 0xea, 0xb6, 0xa4, 0x23, 0xc8, 0xcb, 0x4c, 0x70, 0x5c, 0x6a, 0x7f,
0x45, 0x56, 0x93, 0xed, 0xc7, 0xbd, 0xa9, 0x56, 0xb3, 0x7b, 0x4b, 0x50, 0xad, 0xdf, 0x05, 0x51,
0x55, 0x3f, 0x16, 0xa4, 0x3b, 0xbe, 0xff, 0x1c, 0x00, 0x00, 0xff, 0xff, 0x76, 0xc4, 0xb9, 0x29,
0x1d, 0x0b, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -858,6 +958,7 @@ const _ = grpc.SupportPackageIsVersion4
type SwapServerClient interface {
LoopOutTerms(ctx context.Context, in *ServerLoopOutTermsRequest, opts ...grpc.CallOption) (*ServerLoopOutTerms, error)
NewLoopOutSwap(ctx context.Context, in *ServerLoopOutRequest, opts ...grpc.CallOption) (*ServerLoopOutResponse, error)
LoopOutPushPreimage(ctx context.Context, in *ServerLoopOutPushPreimageRequest, opts ...grpc.CallOption) (*ServerLoopOutPushPreimageResponse, error)
LoopOutQuote(ctx context.Context, in *ServerLoopOutQuoteRequest, opts ...grpc.CallOption) (*ServerLoopOutQuote, error)
LoopInTerms(ctx context.Context, in *ServerLoopInTermsRequest, opts ...grpc.CallOption) (*ServerLoopInTerms, error)
NewLoopInSwap(ctx context.Context, in *ServerLoopInRequest, opts ...grpc.CallOption) (*ServerLoopInResponse, error)
@ -890,6 +991,15 @@ func (c *swapServerClient) NewLoopOutSwap(ctx context.Context, in *ServerLoopOut
return out, nil
}
func (c *swapServerClient) LoopOutPushPreimage(ctx context.Context, in *ServerLoopOutPushPreimageRequest, opts ...grpc.CallOption) (*ServerLoopOutPushPreimageResponse, error) {
out := new(ServerLoopOutPushPreimageResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/LoopOutPushPreimage", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapServerClient) LoopOutQuote(ctx context.Context, in *ServerLoopOutQuoteRequest, opts ...grpc.CallOption) (*ServerLoopOutQuote, error) {
out := new(ServerLoopOutQuote)
err := c.cc.Invoke(ctx, "/looprpc.SwapServer/LoopOutQuote", in, out, opts...)
@ -930,6 +1040,7 @@ func (c *swapServerClient) LoopInQuote(ctx context.Context, in *ServerLoopInQuot
type SwapServerServer interface {
LoopOutTerms(context.Context, *ServerLoopOutTermsRequest) (*ServerLoopOutTerms, error)
NewLoopOutSwap(context.Context, *ServerLoopOutRequest) (*ServerLoopOutResponse, error)
LoopOutPushPreimage(context.Context, *ServerLoopOutPushPreimageRequest) (*ServerLoopOutPushPreimageResponse, error)
LoopOutQuote(context.Context, *ServerLoopOutQuoteRequest) (*ServerLoopOutQuote, error)
LoopInTerms(context.Context, *ServerLoopInTermsRequest) (*ServerLoopInTerms, error)
NewLoopInSwap(context.Context, *ServerLoopInRequest) (*ServerLoopInResponse, error)
@ -976,6 +1087,24 @@ func _SwapServer_NewLoopOutSwap_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _SwapServer_LoopOutPushPreimage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ServerLoopOutPushPreimageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapServerServer).LoopOutPushPreimage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapServer/LoopOutPushPreimage",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapServerServer).LoopOutPushPreimage(ctx, req.(*ServerLoopOutPushPreimageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapServer_LoopOutQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ServerLoopOutQuoteRequest)
if err := dec(in); err != nil {
@ -1060,6 +1189,10 @@ var _SwapServer_serviceDesc = grpc.ServiceDesc{
MethodName: "NewLoopOutSwap",
Handler: _SwapServer_NewLoopOutSwap_Handler,
},
{
MethodName: "LoopOutPushPreimage",
Handler: _SwapServer_LoopOutPushPreimage_Handler,
},
{
MethodName: "LoopOutQuote",
Handler: _SwapServer_LoopOutQuote_Handler,

@ -9,6 +9,9 @@ service SwapServer {
rpc NewLoopOutSwap (ServerLoopOutRequest) returns (ServerLoopOutResponse);
rpc LoopOutPushPreimage (ServerLoopOutPushPreimageRequest)
returns (ServerLoopOutPushPreimageResponse);
rpc LoopOutQuote (ServerLoopOutQuoteRequest) returns (ServerLoopOutQuote);
rpc LoopInTerms (ServerLoopInTermsRequest) returns (ServerLoopInTerms);
@ -37,6 +40,14 @@ enum ProtocolVersion {
published htlcs may use native (P2WSH) or nested (NP2WSH) segwit as well.
*/
NATIVE_SEGWIT_LOOP_IN = 2;
/*
Once the on chain loop out htlc is confirmed, the client can push the swap
preimage to the server to speed up claim of their off chain htlc (acquiring
incoming liquidity more quickly than if the server waited for the on chain
claim tx).
*/
PREIMAGE_PUSH_LOOP_OUT = 3;
}
message ServerLoopOutRequest {
@ -143,3 +154,20 @@ message ServerLoopInTerms {
uint64 min_swap_amount = 1;
uint64 max_swap_amount = 2;
}
// ServerLoopOutPushPreimageRequest pushes a preimage to the server. Note that
// this call returns with no error after the server acknowledges the preimage
// and does not block until the invoice is settled.
message ServerLoopOutPushPreimageRequest {
// The protocol version that the client adheres to.
ProtocolVersion protocol_version = 1;
/*
Preimage is the preimage of the loop out swap that we wish to push to the
server to speed up off-chain claim once the on-chain htlc has confirmed.
*/
bytes preimage = 2;
}
message ServerLoopOutPushPreimageResponse {
}

@ -36,6 +36,9 @@ type serverMock struct {
swapInvoice string
swapHash lntypes.Hash
// preimagePush is a channel that preimage pushes are sent into.
preimagePush chan lntypes.Preimage
}
var _ swapServerClient = (*serverMock)(nil)
@ -49,6 +52,8 @@ func newServerMock() *serverMock {
prepayInvoiceAmt: 100,
height: 600,
preimagePush: make(chan lntypes.Preimage),
}
}
@ -151,6 +156,15 @@ func (s *serverMock) NewLoopInSwap(ctx context.Context,
return resp, nil
}
func (s *serverMock) PushLoopOutPreimage(_ context.Context,
preimage lntypes.Preimage) error {
// Push the preimage into the mock's preimage channel.
s.preimagePush <- preimage
return nil
}
func (s *serverMock) GetLoopInTerms(ctx context.Context) (
*LoopInTerms, error) {

@ -22,7 +22,7 @@ import (
// protocolVersion defines the version of the protocol that is currently
// supported by the loop client.
const protocolVersion = looprpc.ProtocolVersion_NATIVE_SEGWIT_LOOP_IN
const protocolVersion = looprpc.ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT
type swapServerClient interface {
GetLoopOutTerms(ctx context.Context) (
@ -44,6 +44,9 @@ type swapServerClient interface {
swapPublicationDeadline time.Time) (
*newLoopOutResponse, error)
PushLoopOutPreimage(ctx context.Context,
preimage lntypes.Preimage) error
NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount,
senderKey [33]byte, swapInvoice string, lastHop *route.Vertex) (
@ -215,6 +218,23 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
}, nil
}
// PushLoopOutPreimage pushes a preimage to the server.
func (s *grpcSwapServerClient) PushLoopOutPreimage(ctx context.Context,
preimage lntypes.Preimage) error {
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
defer rpcCancel()
_, err := s.server.LoopOutPushPreimage(rpcCtx,
&looprpc.ServerLoopOutPushPreimageRequest{
ProtocolVersion: protocolVersion,
Preimage: preimage[:],
},
)
return err
}
func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
swapHash lntypes.Hash, amount btcutil.Amount, senderKey [33]byte,
swapInvoice string, lastHop *route.Vertex) (*newLoopInResponse, error) {

@ -94,6 +94,24 @@ func (ctx *Context) AssertRegisterSpendNtfn(script []byte) {
}
}
// AssertTrackPayment asserts that a call was made to track payment, and
// returns the track payment message so that it can be used to send updates
// to the test.
func (ctx *Context) AssertTrackPayment() TrackPaymentMessage {
ctx.T.Helper()
var msg TrackPaymentMessage
select {
case msg = <-ctx.Lnd.TrackPaymentChannel:
case <-time.After(Timeout):
DumpGoroutines()
ctx.T.Fatalf("payment not tracked")
}
return msg
}
// AssertRegisterConf asserts that a register for conf has been received.
func (ctx *Context) AssertRegisterConf() *ConfRegistration {
ctx.T.Helper()

@ -7,12 +7,15 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
var (
@ -231,3 +234,30 @@ func (ctx *testContext) publishHtlc(script []byte,
Index: 0,
}
}
// trackPayment asserts that a call to track payment was sent and sends the
// status provided into the updates channel.
func (ctx *testContext) trackPayment(status lnrpc.Payment_PaymentStatus) {
trackPayment := ctx.Context.AssertTrackPayment()
select {
case trackPayment.Updates <- lndclient.PaymentStatus{
State: status,
}:
case <-time.After(test.Timeout):
ctx.T.Fatalf("could not send payment update")
}
}
// assertPreimagePush asserts that we made an attempt to push our preimage to
// the server.
func (ctx *testContext) assertPreimagePush(preimage lntypes.Preimage) {
select {
case pushedPreimage := <-ctx.serverMock.preimagePush:
require.Equal(ctx.T, preimage, pushedPreimage)
case <-time.After(test.Timeout):
ctx.T.Fatalf("preimage not pushed")
}
}

Loading…
Cancel
Save