mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Merge pull request #634 from GeorgeTsagk/sweep-batcher
Loop Out Sweep Batcher
This commit is contained in:
commit
df2db8055b
54 changed files with 6014 additions and 1813 deletions
48
client.go
48
client.go
|
|
@ -17,6 +17,8 @@ import (
|
|||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -60,7 +62,7 @@ var (
|
|||
// probeTimeout is the maximum time until a probe is allowed to take.
|
||||
probeTimeout = 3 * time.Minute
|
||||
|
||||
republishDelay = 10 * time.Second
|
||||
repushDelay = 1 * time.Second
|
||||
|
||||
// MinerFeeEstimationFailed is a magic number that is returned in a
|
||||
// quote call as the miner fee if the fee estimation in lnd's wallet
|
||||
|
|
@ -133,7 +135,8 @@ type ClientConfig struct {
|
|||
|
||||
// NewClient returns a new instance to initiate swaps with.
|
||||
func NewClient(dbDir string, loopDB loopdb.SwapStore,
|
||||
cfg *ClientConfig) (*Client, func(), error) {
|
||||
sweeperDb sweepbatcher.BatcherStore, cfg *ClientConfig) (
|
||||
*Client, func(), error) {
|
||||
|
||||
lsatStore, err := lsat.NewFileStore(dbDir)
|
||||
if err != nil {
|
||||
|
|
@ -161,27 +164,36 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
|
|||
Lnd: cfg.Lnd,
|
||||
}
|
||||
|
||||
verifySchnorrSig := func(pubKey *btcec.PublicKey, hash, sig []byte) error {
|
||||
schnorrSig, err := schnorr.ParseSignature(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !schnorrSig.Verify(hash, pubKey) {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
batcher := sweepbatcher.NewBatcher(
|
||||
cfg.Lnd.WalletKit, cfg.Lnd.ChainNotifier, cfg.Lnd.Signer,
|
||||
swapServerClient.MultiMuSig2SignSweep, verifySchnorrSig,
|
||||
cfg.Lnd.ChainParams, sweeperDb, loopDB,
|
||||
)
|
||||
|
||||
executor := newExecutor(&executorConfig{
|
||||
lnd: cfg.Lnd,
|
||||
store: loopDB,
|
||||
sweeper: sweeper,
|
||||
batcher: batcher,
|
||||
createExpiryTimer: config.CreateExpiryTimer,
|
||||
loopOutMaxParts: cfg.LoopOutMaxParts,
|
||||
totalPaymentTimeout: cfg.TotalPaymentTimeout,
|
||||
maxPaymentRetries: cfg.MaxPaymentRetries,
|
||||
cancelSwap: swapServerClient.CancelLoopOutSwap,
|
||||
verifySchnorrSig: func(pubKey *btcec.PublicKey, hash, sig []byte) error {
|
||||
schnorrSig, err := schnorr.ParseSignature(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !schnorrSig.Verify(hash, pubKey) {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
verifySchnorrSig: verifySchnorrSig,
|
||||
})
|
||||
|
||||
client := &Client{
|
||||
|
|
@ -232,7 +244,7 @@ func (s *Client) FetchSwaps(ctx context.Context) ([]*SwapInfo, error) {
|
|||
LastUpdate: swp.LastUpdateTime(),
|
||||
}
|
||||
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
swp.Hash, &swp.Contract.SwapContract,
|
||||
s.lndServices.ChainParams,
|
||||
)
|
||||
|
|
@ -265,7 +277,7 @@ func (s *Client) FetchSwaps(ctx context.Context) ([]*SwapInfo, error) {
|
|||
LastUpdate: swp.LastUpdateTime(),
|
||||
}
|
||||
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
swp.Hash, &swp.Contract.SwapContract,
|
||||
s.lndServices.ChainParams,
|
||||
)
|
||||
|
|
@ -540,7 +552,7 @@ func (s *Client) getLoopOutSweepFee(ctx context.Context, confTarget int32) (
|
|||
return 0, err
|
||||
}
|
||||
|
||||
scriptVersion := GetHtlcScriptVersion(
|
||||
scriptVersion := utils.GetHtlcScriptVersion(
|
||||
loopdb.CurrentProtocolVersion(),
|
||||
)
|
||||
|
||||
|
|
@ -731,7 +743,7 @@ func (s *Client) estimateFee(ctx context.Context, amt btcutil.Amount,
|
|||
// Generate a dummy address for fee estimation.
|
||||
witnessProg := [32]byte{}
|
||||
|
||||
scriptVersion := GetHtlcScriptVersion(
|
||||
scriptVersion := utils.GetHtlcScriptVersion(
|
||||
loopdb.CurrentProtocolVersion(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -146,8 +147,6 @@ func TestLoopOutFailWrongAmount(t *testing.T) {
|
|||
// TestLoopOutResume tests that swaps in various states are properly resumed
|
||||
// after a restart.
|
||||
func TestLoopOutResume(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
defaultConfs := loopdb.DefaultLoopOutHtlcConfirmations
|
||||
|
||||
storedVersion := []loopdb.ProtocolVersion{
|
||||
|
|
@ -279,7 +278,7 @@ func testLoopOutResume(t *testing.T, confs uint32, expired, preimageRevealed,
|
|||
preimageRevealed, int32(confs),
|
||||
)
|
||||
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
hash, &pendingSwap.Contract.SwapContract,
|
||||
&chaincfg.TestNet3Params,
|
||||
)
|
||||
|
|
@ -304,7 +303,7 @@ func testLoopOutResume(t *testing.T, confs uint32, expired, preimageRevealed,
|
|||
func(r error) {},
|
||||
func(r error) {},
|
||||
preimageRevealed,
|
||||
confIntent, GetHtlcScriptVersion(protocolVersion),
|
||||
confIntent, utils.GetHtlcScriptVersion(protocolVersion),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -317,15 +316,28 @@ func testLoopOutSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
|
|||
|
||||
signalPrepaymentResult(nil)
|
||||
|
||||
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)
|
||||
|
||||
// We need to notify the height, as the loopout is going to attempt a
|
||||
// sweep when a new block is received.
|
||||
err := ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 1)
|
||||
require.NoError(ctx.Context.T, err)
|
||||
|
||||
// Publish tick.
|
||||
ctx.expiryChan <- testTime
|
||||
|
||||
// One spend notifier is registered by batch to watch primary sweep.
|
||||
ctx.AssertRegisterSpendNtfn(confIntent.PkScript)
|
||||
|
||||
ctx.AssertEpochListeners(2)
|
||||
|
||||
// Mock the blockheight again as that's when the batch will broadcast
|
||||
// the tx.
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 1)
|
||||
require.NoError(ctx.Context.T, err)
|
||||
|
||||
// Expect a signing request in the non taproot case.
|
||||
if scriptVersion != swap.HtlcV3 {
|
||||
<-ctx.Context.Lnd.SignOutputRawChannel
|
||||
|
|
@ -340,14 +352,7 @@ func testLoopOutSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
|
|||
// preimage before sweeping in order for the server to trust us with
|
||||
// our MuSig2 signing attempts.
|
||||
if scriptVersion == swap.HtlcV3 {
|
||||
ctx.assertPreimagePush(ctx.store.loopOutSwaps[hash].Preimage)
|
||||
|
||||
// Try MuSig2 signing first and fail it so that we go for a
|
||||
// normal sweep.
|
||||
for i := 0; i < maxMusigSweepRetries; i++ {
|
||||
ctx.expiryChan <- testTime
|
||||
ctx.assertPreimagePush(ctx.store.loopOutSwaps[hash].Preimage)
|
||||
}
|
||||
ctx.assertPreimagePush(ctx.store.LoopOutSwaps[hash].Preimage)
|
||||
<-ctx.Context.Lnd.SignOutputRawChannel
|
||||
}
|
||||
|
||||
|
|
@ -388,6 +393,8 @@ func testLoopOutSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
|
|||
|
||||
ctx.NotifySpend(sweepTx, 0)
|
||||
|
||||
ctx.AssertRegisterConf(true, 3)
|
||||
|
||||
ctx.assertStatus(loopdb.StateSuccess)
|
||||
|
||||
ctx.assertStoreFinished(loopdb.StateSuccess)
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ func loopOut(ctx *cli.Context) error {
|
|||
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
|
||||
Amt: int64(amt),
|
||||
Dest: destAddr,
|
||||
IsExternalAddr: destAddr != "",
|
||||
Account: account,
|
||||
AccountAddrType: accountAddrType,
|
||||
MaxMinerFee: int64(limits.maxMinerFee),
|
||||
|
|
|
|||
23
executor.go
23
executor.go
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
)
|
||||
|
|
@ -23,6 +24,8 @@ type executorConfig struct {
|
|||
|
||||
sweeper *sweep.Sweeper
|
||||
|
||||
batcher *sweepbatcher.Batcher
|
||||
|
||||
store loopdb.SwapStore
|
||||
|
||||
createExpiryTimer func(expiry time.Duration) <-chan time.Time
|
||||
|
|
@ -71,6 +74,7 @@ func (s *executor) run(mainCtx context.Context,
|
|||
err error
|
||||
blockEpochChan <-chan int32
|
||||
blockErrorChan <-chan error
|
||||
batcherErrChan chan error
|
||||
)
|
||||
|
||||
for {
|
||||
|
|
@ -121,6 +125,21 @@ func (s *executor) run(mainCtx context.Context,
|
|||
return mainCtx.Err()
|
||||
}
|
||||
|
||||
batcherErrChan = make(chan error, 1)
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
|
||||
err := s.batcher.Run(mainCtx)
|
||||
if err != nil {
|
||||
select {
|
||||
case batcherErrChan <- err:
|
||||
case <-mainCtx.Done():
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Start main event loop.
|
||||
log.Infof("Starting event loop at height %v", height)
|
||||
|
||||
|
|
@ -156,6 +175,7 @@ func (s *executor) run(mainCtx context.Context,
|
|||
err := newSwap.execute(mainCtx, &executeConfig{
|
||||
statusChan: statusChan,
|
||||
sweeper: s.sweeper,
|
||||
batcher: s.batcher,
|
||||
blockEpochChan: queue.ChanOut(),
|
||||
timerFactory: s.executorConfig.createExpiryTimer,
|
||||
loopOutMaxParts: s.executorConfig.loopOutMaxParts,
|
||||
|
|
@ -211,6 +231,9 @@ func (s *executor) run(mainCtx context.Context,
|
|||
case err := <-blockErrorChan:
|
||||
return fmt.Errorf("block error: %v", err)
|
||||
|
||||
case err := <-batcherErrChan:
|
||||
return fmt.Errorf("batcher error: %v", err)
|
||||
|
||||
case <-mainCtx.Done():
|
||||
return mainCtx.Err()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ type OutRequest struct {
|
|||
// Destination address for the swap.
|
||||
DestAddr btcutil.Address
|
||||
|
||||
// IsExternalAddr indicates whether the provided destination address
|
||||
// does not belong to the underlying wallet. This helps indicate
|
||||
// whether the sweep of this swap can be batched or not.
|
||||
IsExternalAddr bool
|
||||
|
||||
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
|
||||
// paid for payment to the server. This limit is applied during path
|
||||
// finding. Typically this value is taken from the response of the
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ const (
|
|||
// loopInTimeout is the label used for loop in swaps to sweep an HTLC
|
||||
// that has timed out.
|
||||
loopInSweepTimeout = "InSweepTimeout"
|
||||
|
||||
loopOutBatchSweepSuccess = "BatchOutSweepSuccess -- %d"
|
||||
)
|
||||
|
||||
// LoopOutSweepSuccess returns the label used for loop out swaps to sweep the
|
||||
|
|
@ -25,6 +27,11 @@ func LoopOutSweepSuccess(swapHash string) string {
|
|||
return fmt.Sprintf(loopdLabelPattern, loopOutSweepSuccess, swapHash)
|
||||
}
|
||||
|
||||
// LoopOutBatchSweepSuccess returns the label used for loop out sweep batcher.
|
||||
func LoopOutBatchSweepSuccess(batchID int32) string {
|
||||
return fmt.Sprintf(loopOutBatchSweepSuccess, batchID)
|
||||
}
|
||||
|
||||
// LoopInHtlcLabel returns the label used for loop in swaps to publish an HTLC.
|
||||
func LoopInHtlcLabel(swapHash string) string {
|
||||
return fmt.Sprintf(loopdLabelPattern, loopInHtlc, swapHash)
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ func TestAutoloopAddress(t *testing.T) {
|
|||
Amount: amt,
|
||||
// Define the expected destination address.
|
||||
DestAddr: addr,
|
||||
IsExternalAddr: true,
|
||||
MaxSwapRoutingFee: maxRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
quote1.PrepayAmount, prepayFeePPM,
|
||||
|
|
@ -439,6 +440,7 @@ func TestAutoloopAddress(t *testing.T) {
|
|||
Amount: amt,
|
||||
// Define the expected destination address.
|
||||
DestAddr: addr,
|
||||
IsExternalAddr: true,
|
||||
MaxSwapRoutingFee: maxRouteFee,
|
||||
MaxPrepayRoutingFee: ppmToSat(
|
||||
quote2.PrepayAmount, routeFeePPM,
|
||||
|
|
|
|||
|
|
@ -450,6 +450,13 @@ func (m *Manager) autoloop(ctx context.Context) error {
|
|||
// Create a copy of our range var so that we can reference it.
|
||||
swap := swap
|
||||
|
||||
// Check if the parameter for custom address is defined for loop
|
||||
// outs.
|
||||
if m.params.DestAddr != nil {
|
||||
swap.DestAddr = m.params.DestAddr
|
||||
swap.IsExternalAddr = true
|
||||
}
|
||||
|
||||
go m.dispatchStickyLoopOut(
|
||||
ctx, swap, defaultAmountBackoffRetry,
|
||||
defaultAmountBackoff,
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ func (b *loopOutBuilder) buildSwap(ctx context.Context, pubkey route.Vertex,
|
|||
// already validated them.
|
||||
request := loop.OutRequest{
|
||||
Amount: amount,
|
||||
IsExternalAddr: false,
|
||||
OutgoingChanSet: chanSet,
|
||||
MaxPrepayRoutingFee: prepayMaxFee,
|
||||
MaxSwapRoutingFee: routeMaxFee,
|
||||
|
|
@ -160,9 +161,11 @@ func (b *loopOutBuilder) buildSwap(ctx context.Context, pubkey route.Vertex,
|
|||
if len(params.Account) > 0 {
|
||||
account = params.Account
|
||||
addrType = params.AccountAddrType
|
||||
request.IsExternalAddr = true
|
||||
}
|
||||
if params.DestAddr != nil {
|
||||
request.DestAddr = params.DestAddr
|
||||
request.IsExternalAddr = true
|
||||
} else {
|
||||
addr, err := b.cfg.Lnd.WalletKit.NextAddr(
|
||||
ctx, account, addrType, false,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/loopd/perms"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
|
||||
"github.com/lightninglabs/loop/instantout/reservation"
|
||||
loop_looprpc "github.com/lightninglabs/loop/looprpc"
|
||||
|
|
@ -412,9 +413,11 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
return err
|
||||
}
|
||||
|
||||
sweeperDb := sweepbatcher.NewSQLStore(baseDb, chainParams)
|
||||
|
||||
// Create an instance of the loop client library.
|
||||
swapClient, clientCleanup, err := getClient(
|
||||
d.cfg, swapDb, &d.lnd.LndServices,
|
||||
d.cfg, swapDb, sweeperDb, &d.lnd.LndServices,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/lightninglabs/loop/instantout/reservation"
|
||||
"github.com/lightninglabs/loop/liquidity"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightningnetwork/lnd"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
|
|
@ -32,6 +33,7 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
|
|||
|
||||
lnd.SetSubLogger(root, Subsystem, log)
|
||||
lnd.AddSubLogger(root, "LOOP", intercept, loop.UseLogger)
|
||||
lnd.AddSubLogger(root, "SWEEP", intercept, sweepbatcher.UseLogger)
|
||||
lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger)
|
||||
lnd.AddSubLogger(root, "STORE", intercept, loopdb.UseLogger)
|
||||
lnd.AddSubLogger(root, lsat.Subsystem, intercept, lsat.UseLogger)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
|
|||
log.Infof("Loop out request received")
|
||||
|
||||
var sweepAddr btcutil.Address
|
||||
var isExternalAddr bool
|
||||
var err error
|
||||
//nolint:lll
|
||||
switch {
|
||||
|
|
@ -117,6 +118,8 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
|
|||
return nil, fmt.Errorf("decode address: %v", err)
|
||||
}
|
||||
|
||||
isExternalAddr = true
|
||||
|
||||
case in.Account != "" && in.AccountAddrType == clientrpc.AddressType_ADDRESS_TYPE_UNKNOWN:
|
||||
return nil, liquidity.ErrAccountAndAddrType
|
||||
|
||||
|
|
@ -141,6 +144,8 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
|
|||
"%v", err)
|
||||
}
|
||||
|
||||
isExternalAddr = true
|
||||
|
||||
default:
|
||||
// Generate sweep address if none specified.
|
||||
sweepAddr, err = s.lnd.WalletKit.NextAddr(
|
||||
|
|
@ -166,6 +171,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
|
|||
req := &loop.OutRequest{
|
||||
Amount: btcutil.Amount(in.Amt),
|
||||
DestAddr: sweepAddr,
|
||||
IsExternalAddr: isExternalAddr,
|
||||
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
|
||||
MaxPrepayAmount: btcutil.Amount(in.MaxPrepayAmt),
|
||||
MaxPrepayRoutingFee: btcutil.Amount(in.MaxPrepayRoutingFee),
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import (
|
|||
"github.com/lightninglabs/loop/liquidity"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
)
|
||||
|
||||
// getClient returns an instance of the swap client.
|
||||
func getClient(cfg *Config, swapDb loopdb.SwapStore,
|
||||
lnd *lndclient.LndServices) (*loop.Client, func(), error) {
|
||||
sweeperDb sweepbatcher.BatcherStore, lnd *lndclient.LndServices) (
|
||||
*loop.Client, func(), error) {
|
||||
|
||||
clientConfig := &loop.ClientConfig{
|
||||
ServerAddress: cfg.Server.Host,
|
||||
|
|
@ -33,7 +35,7 @@ func getClient(cfg *Config, swapDb loopdb.SwapStore,
|
|||
}
|
||||
|
||||
swapClient, cleanUp, err := loop.NewClient(
|
||||
cfg.DataDir, swapDb, clientConfig,
|
||||
cfg.DataDir, swapDb, sweeperDb, clientConfig,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
)
|
||||
|
||||
// view prints all swaps currently in the database.
|
||||
|
|
@ -25,12 +27,16 @@ func view(config *Config, lisCfg *ListenerCfg) error {
|
|||
return err
|
||||
}
|
||||
|
||||
swapDb, _, err := openDatabase(config, chainParams)
|
||||
swapDb, baseDb, err := openDatabase(config, chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
swapClient, cleanup, err := getClient(config, swapDb, &lnd.LndServices)
|
||||
sweeperDb := sweepbatcher.NewSQLStore(baseDb, chainParams)
|
||||
|
||||
swapClient, cleanup, err := getClient(
|
||||
config, swapDb, sweeperDb, &lnd.LndServices,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -56,7 +62,7 @@ func viewOut(swapClient *loop.Client, chainParams *chaincfg.Params) error {
|
|||
for _, s := range swaps {
|
||||
s := s
|
||||
|
||||
htlc, err := loop.GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
s.Hash, &s.Contract.SwapContract, chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -107,7 +113,7 @@ func viewIn(swapClient *loop.Client, chainParams *chaincfg.Params) error {
|
|||
for _, s := range swaps {
|
||||
s := s
|
||||
|
||||
htlc, err := loop.GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
s.Hash, &s.Contract.SwapContract, chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ type LoopOutContract struct {
|
|||
// DestAddr is the destination address of the loop out swap.
|
||||
DestAddr btcutil.Address
|
||||
|
||||
// IsExternalAddr indicates whether the destination address does not
|
||||
// belong to the backing lnd node.
|
||||
IsExternalAddr bool
|
||||
|
||||
// SwapInvoice is the invoice that is to be paid by the client to
|
||||
// initiate the loop out swap.
|
||||
SwapInvoice string
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightninglabs/loop/loopdb/sqlc"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
|
|
@ -31,13 +32,16 @@ func (s *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut,
|
|||
loopOuts = make([]*LoopOut, len(swaps))
|
||||
|
||||
for i, swap := range swaps {
|
||||
updates, err := s.Queries.GetSwapUpdates(ctx, swap.SwapHash)
|
||||
updates, err := s.Queries.GetSwapUpdates(
|
||||
ctx, swap.SwapHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loopOut, err := s.convertLoopOutRow(
|
||||
sqlc.GetLoopOutSwapRow(swap), updates,
|
||||
loopOut, err := ConvertLoopOutRow(
|
||||
s.network, sqlc.GetLoopOutSwapRow(swap),
|
||||
updates,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -72,8 +76,8 @@ func (s *BaseDB) FetchLoopOutSwap(ctx context.Context,
|
|||
return err
|
||||
}
|
||||
|
||||
loopOut, err = s.convertLoopOutRow(
|
||||
swap, updates,
|
||||
loopOut, err = ConvertLoopOutRow(
|
||||
s.network, swap, updates,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -430,6 +434,7 @@ func loopOutToInsertArgs(hash lntypes.Hash,
|
|||
return sqlc.InsertLoopOutParams{
|
||||
SwapHash: hash[:],
|
||||
DestAddress: loopOut.DestAddr.String(),
|
||||
SingleSweep: loopOut.IsExternalAddr,
|
||||
SwapInvoice: loopOut.SwapInvoice,
|
||||
MaxSwapRoutingFee: int64(loopOut.MaxSwapRoutingFee),
|
||||
SweepConfTarget: loopOut.SweepConfTarget,
|
||||
|
|
@ -479,9 +484,9 @@ func swapToHtlcKeysInsertArgs(hash lntypes.Hash,
|
|||
}
|
||||
}
|
||||
|
||||
// convertLoopOutRow converts a database row containing a loop out swap to a
|
||||
// ConvertLoopOutRow converts a database row containing a loop out swap to a
|
||||
// LoopOut struct.
|
||||
func (s *BaseDB) convertLoopOutRow(row sqlc.GetLoopOutSwapRow,
|
||||
func ConvertLoopOutRow(network *chaincfg.Params, row sqlc.GetLoopOutSwapRow,
|
||||
updates []sqlc.SwapUpdate) (*LoopOut, error) {
|
||||
|
||||
htlcKeys, err := fetchHtlcKeys(
|
||||
|
|
@ -498,7 +503,7 @@ func (s *BaseDB) convertLoopOutRow(row sqlc.GetLoopOutSwapRow,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
destAddress, err := btcutil.DecodeAddress(row.DestAddress, s.network)
|
||||
destAddress, err := btcutil.DecodeAddress(row.DestAddress, network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -523,6 +528,7 @@ func (s *BaseDB) convertLoopOutRow(row sqlc.GetLoopOutSwapRow,
|
|||
ProtocolVersion: ProtocolVersion(row.ProtocolVersion),
|
||||
},
|
||||
DestAddr: destAddress,
|
||||
IsExternalAddr: row.SingleSweep,
|
||||
SwapInvoice: row.SwapInvoice,
|
||||
MaxSwapRoutingFee: btcutil.Amount(row.MaxSwapRoutingFee),
|
||||
SweepConfTarget: row.SweepConfTarget,
|
||||
|
|
|
|||
317
loopdb/sqlc/batch.sql.go
Normal file
317
loopdb/sqlc/batch.sql.go
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.17.2
|
||||
// source: batch.sql
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
const confirmBatch = `-- name: ConfirmBatch :exec
|
||||
UPDATE
|
||||
sweep_batches
|
||||
SET
|
||||
confirmed = TRUE
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ConfirmBatch(ctx context.Context, id int32) error {
|
||||
_, err := q.db.ExecContext(ctx, confirmBatch, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getBatchSweeps = `-- name: GetBatchSweeps :many
|
||||
SELECT
|
||||
sweeps.id, sweeps.swap_hash, sweeps.batch_id, sweeps.outpoint_txid, sweeps.outpoint_index, sweeps.amt, sweeps.completed,
|
||||
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
|
||||
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep,
|
||||
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
|
||||
FROM
|
||||
sweeps
|
||||
JOIN
|
||||
swaps ON sweeps.swap_hash = swaps.swap_hash
|
||||
JOIN
|
||||
loopout_swaps ON sweeps.swap_hash = loopout_swaps.swap_hash
|
||||
JOIN
|
||||
htlc_keys ON sweeps.swap_hash = htlc_keys.swap_hash
|
||||
WHERE
|
||||
sweeps.batch_id = $1
|
||||
ORDER BY
|
||||
sweeps.id ASC
|
||||
`
|
||||
|
||||
type GetBatchSweepsRow struct {
|
||||
ID int32
|
||||
SwapHash []byte
|
||||
BatchID int32
|
||||
OutpointTxid []byte
|
||||
OutpointIndex int32
|
||||
Amt int64
|
||||
Completed bool
|
||||
ID_2 int32
|
||||
SwapHash_2 []byte
|
||||
Preimage []byte
|
||||
InitiationTime time.Time
|
||||
AmountRequested int64
|
||||
CltvExpiry int32
|
||||
MaxMinerFee int64
|
||||
MaxSwapFee int64
|
||||
InitiationHeight int32
|
||||
ProtocolVersion int32
|
||||
Label string
|
||||
SwapHash_3 []byte
|
||||
DestAddress string
|
||||
SwapInvoice string
|
||||
MaxSwapRoutingFee int64
|
||||
SweepConfTarget int32
|
||||
HtlcConfirmations int32
|
||||
OutgoingChanSet string
|
||||
PrepayInvoice string
|
||||
MaxPrepayRoutingFee int64
|
||||
PublicationDeadline time.Time
|
||||
SingleSweep bool
|
||||
SwapHash_4 []byte
|
||||
SenderScriptPubkey []byte
|
||||
ReceiverScriptPubkey []byte
|
||||
SenderInternalPubkey []byte
|
||||
ReceiverInternalPubkey []byte
|
||||
ClientKeyFamily int32
|
||||
ClientKeyIndex int32
|
||||
}
|
||||
|
||||
func (q *Queries) GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatchSweepsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getBatchSweeps, batchID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetBatchSweepsRow
|
||||
for rows.Next() {
|
||||
var i GetBatchSweepsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.SwapHash,
|
||||
&i.BatchID,
|
||||
&i.OutpointTxid,
|
||||
&i.OutpointIndex,
|
||||
&i.Amt,
|
||||
&i.Completed,
|
||||
&i.ID_2,
|
||||
&i.SwapHash_2,
|
||||
&i.Preimage,
|
||||
&i.InitiationTime,
|
||||
&i.AmountRequested,
|
||||
&i.CltvExpiry,
|
||||
&i.MaxMinerFee,
|
||||
&i.MaxSwapFee,
|
||||
&i.InitiationHeight,
|
||||
&i.ProtocolVersion,
|
||||
&i.Label,
|
||||
&i.SwapHash_3,
|
||||
&i.DestAddress,
|
||||
&i.SwapInvoice,
|
||||
&i.MaxSwapRoutingFee,
|
||||
&i.SweepConfTarget,
|
||||
&i.HtlcConfirmations,
|
||||
&i.OutgoingChanSet,
|
||||
&i.PrepayInvoice,
|
||||
&i.MaxPrepayRoutingFee,
|
||||
&i.PublicationDeadline,
|
||||
&i.SingleSweep,
|
||||
&i.SwapHash_4,
|
||||
&i.SenderScriptPubkey,
|
||||
&i.ReceiverScriptPubkey,
|
||||
&i.SenderInternalPubkey,
|
||||
&i.ReceiverInternalPubkey,
|
||||
&i.ClientKeyFamily,
|
||||
&i.ClientKeyIndex,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSweepStatus = `-- name: GetSweepStatus :one
|
||||
SELECT
|
||||
COALESCE(s.completed, f.false_value) AS completed
|
||||
FROM
|
||||
(SELECT false AS false_value) AS f
|
||||
LEFT JOIN
|
||||
sweeps s ON s.swap_hash = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSweepStatus(ctx context.Context, swapHash []byte) (bool, error) {
|
||||
row := q.db.QueryRowContext(ctx, getSweepStatus, swapHash)
|
||||
var completed bool
|
||||
err := row.Scan(&completed)
|
||||
return completed, err
|
||||
}
|
||||
|
||||
const getUnconfirmedBatches = `-- name: GetUnconfirmedBatches :many
|
||||
SELECT
|
||||
id, confirmed, batch_tx_id, batch_pk_script, last_rbf_height, last_rbf_sat_per_kw, max_timeout_distance
|
||||
FROM
|
||||
sweep_batches
|
||||
WHERE
|
||||
confirmed = FALSE
|
||||
`
|
||||
|
||||
func (q *Queries) GetUnconfirmedBatches(ctx context.Context) ([]SweepBatch, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getUnconfirmedBatches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []SweepBatch
|
||||
for rows.Next() {
|
||||
var i SweepBatch
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Confirmed,
|
||||
&i.BatchTxID,
|
||||
&i.BatchPkScript,
|
||||
&i.LastRbfHeight,
|
||||
&i.LastRbfSatPerKw,
|
||||
&i.MaxTimeoutDistance,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertBatch = `-- name: InsertBatch :one
|
||||
INSERT INTO sweep_batches (
|
||||
confirmed,
|
||||
batch_tx_id,
|
||||
batch_pk_script,
|
||||
last_rbf_height,
|
||||
last_rbf_sat_per_kw,
|
||||
max_timeout_distance
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6
|
||||
) RETURNING id
|
||||
`
|
||||
|
||||
type InsertBatchParams struct {
|
||||
Confirmed bool
|
||||
BatchTxID sql.NullString
|
||||
BatchPkScript []byte
|
||||
LastRbfHeight sql.NullInt32
|
||||
LastRbfSatPerKw sql.NullInt32
|
||||
MaxTimeoutDistance int32
|
||||
}
|
||||
|
||||
func (q *Queries) InsertBatch(ctx context.Context, arg InsertBatchParams) (int32, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertBatch,
|
||||
arg.Confirmed,
|
||||
arg.BatchTxID,
|
||||
arg.BatchPkScript,
|
||||
arg.LastRbfHeight,
|
||||
arg.LastRbfSatPerKw,
|
||||
arg.MaxTimeoutDistance,
|
||||
)
|
||||
var id int32
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const updateBatch = `-- name: UpdateBatch :exec
|
||||
UPDATE sweep_batches SET
|
||||
confirmed = $2,
|
||||
batch_tx_id = $3,
|
||||
batch_pk_script = $4,
|
||||
last_rbf_height = $5,
|
||||
last_rbf_sat_per_kw = $6
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateBatchParams struct {
|
||||
ID int32
|
||||
Confirmed bool
|
||||
BatchTxID sql.NullString
|
||||
BatchPkScript []byte
|
||||
LastRbfHeight sql.NullInt32
|
||||
LastRbfSatPerKw sql.NullInt32
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBatch(ctx context.Context, arg UpdateBatchParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateBatch,
|
||||
arg.ID,
|
||||
arg.Confirmed,
|
||||
arg.BatchTxID,
|
||||
arg.BatchPkScript,
|
||||
arg.LastRbfHeight,
|
||||
arg.LastRbfSatPerKw,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertSweep = `-- name: UpsertSweep :exec
|
||||
INSERT INTO sweeps (
|
||||
swap_hash,
|
||||
batch_id,
|
||||
outpoint_txid,
|
||||
outpoint_index,
|
||||
amt,
|
||||
completed
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6
|
||||
) ON CONFLICT (swap_hash) DO UPDATE SET
|
||||
batch_id = $2,
|
||||
outpoint_txid = $3,
|
||||
outpoint_index = $4,
|
||||
amt = $5,
|
||||
completed = $6
|
||||
`
|
||||
|
||||
type UpsertSweepParams struct {
|
||||
SwapHash []byte
|
||||
BatchID int32
|
||||
OutpointTxid []byte
|
||||
OutpointIndex int32
|
||||
Amt int64
|
||||
Completed bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertSweep(ctx context.Context, arg UpsertSweepParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertSweep,
|
||||
arg.SwapHash,
|
||||
arg.BatchID,
|
||||
arg.OutpointTxid,
|
||||
arg.OutpointIndex,
|
||||
arg.Amt,
|
||||
arg.Completed,
|
||||
)
|
||||
return err
|
||||
}
|
||||
1
loopdb/sqlc/migrations/000004_is_wallet_addr.down.sql
Normal file
1
loopdb/sqlc/migrations/000004_is_wallet_addr.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE loopout_swaps DROP COLUMN single_sweep;
|
||||
4
loopdb/sqlc/migrations/000004_is_wallet_addr.up.sql
Normal file
4
loopdb/sqlc/migrations/000004_is_wallet_addr.up.sql
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
-- is_external_addr indicates whether the destination address of the swap is not
|
||||
-- a wallet address. The default value used is TRUE in order to maintain the old
|
||||
-- behavior of swaps which doesn't override the destination address.
|
||||
ALTER TABLE loopout_swaps ADD single_sweep BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
2
loopdb/sqlc/migrations/000005_sweep_batcher.down.sql
Normal file
2
loopdb/sqlc/migrations/000005_sweep_batcher.down.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE sweep_batches;
|
||||
DROP TABLE sweeps;
|
||||
58
loopdb/sqlc/migrations/000005_sweep_batcher.up.sql
Normal file
58
loopdb/sqlc/migrations/000005_sweep_batcher.up.sql
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
-- sweep_batches stores the on-going swaps that are batched together.
|
||||
CREATE TABLE sweep_batches (
|
||||
-- id is the autoincrementing primary key of the batch.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- confirmed indicates whether this batch is confirmed.
|
||||
confirmed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- batch_tx_id is the transaction id of the batch transaction.
|
||||
batch_tx_id TEXT,
|
||||
|
||||
-- batch_pk_script is the pkscript of the batch transaction's output.
|
||||
batch_pk_script BLOB,
|
||||
|
||||
-- last_rbf_height was the last height at which we attempted to publish
|
||||
-- an rbf replacement transaction.
|
||||
last_rbf_height INTEGER,
|
||||
|
||||
-- last_rbf_sat_per_kw was the last sat per kw fee rate we used for the
|
||||
-- last published transaction.
|
||||
last_rbf_sat_per_kw INTEGER,
|
||||
|
||||
-- max_timeout_distance is the maximum distance the timeouts of the
|
||||
-- sweeps can have in the batch.
|
||||
max_timeout_distance INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- sweeps stores the individual sweeps that are part of a batch.
|
||||
CREATE TABLE sweeps (
|
||||
-- id is the autoincrementing primary key.
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- swap_hash is the hash of the swap that is being swept.
|
||||
swap_hash BLOB NOT NULL UNIQUE,
|
||||
|
||||
-- batch_id is the id of the batch this swap is part of.
|
||||
batch_id INTEGER NOT NULL,
|
||||
|
||||
-- outpoint_txid is the transaction id of the output being swept.
|
||||
outpoint_txid BLOB NOT NULL,
|
||||
|
||||
-- outpoint_index is the index of the output being swept.
|
||||
outpoint_index INTEGER NOT NULL,
|
||||
|
||||
-- amt is the amount of the output being swept.
|
||||
amt BIGINT NOT NULL,
|
||||
|
||||
-- completed indicates whether the sweep has been completed.
|
||||
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- Foreign key constraint to ensure that we reference an existing batch
|
||||
-- id.
|
||||
FOREIGN KEY (batch_id) REFERENCES sweep_batches(id),
|
||||
|
||||
-- Foreign key constraint to ensure that swap_hash references an
|
||||
-- existing swap.
|
||||
FOREIGN KEY (swap_hash) REFERENCES swaps(swap_hash)
|
||||
);
|
||||
|
|
@ -42,6 +42,7 @@ type LoopoutSwap struct {
|
|||
PrepayInvoice string
|
||||
MaxPrepayRoutingFee int64
|
||||
PublicationDeadline time.Time
|
||||
SingleSweep bool
|
||||
}
|
||||
|
||||
type Reservation struct {
|
||||
|
|
@ -90,3 +91,23 @@ type SwapUpdate struct {
|
|||
OnchainCost int64
|
||||
OffchainCost int64
|
||||
}
|
||||
|
||||
type Sweep struct {
|
||||
ID int32
|
||||
SwapHash []byte
|
||||
BatchID int32
|
||||
OutpointTxid []byte
|
||||
OutpointIndex int32
|
||||
Amt int64
|
||||
Completed bool
|
||||
}
|
||||
|
||||
type SweepBatch struct {
|
||||
ID int32
|
||||
Confirmed bool
|
||||
BatchTxID sql.NullString
|
||||
BatchPkScript []byte
|
||||
LastRbfHeight sql.NullInt32
|
||||
LastRbfSatPerKw sql.NullInt32
|
||||
MaxTimeoutDistance int32
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import (
|
|||
)
|
||||
|
||||
type Querier interface {
|
||||
ConfirmBatch(ctx context.Context, id int32) error
|
||||
CreateReservation(ctx context.Context, arg CreateReservationParams) error
|
||||
FetchLiquidityParams(ctx context.Context) ([]byte, error)
|
||||
GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatchSweepsRow, error)
|
||||
GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error)
|
||||
GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error)
|
||||
GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error)
|
||||
|
|
@ -19,14 +21,19 @@ type Querier interface {
|
|||
GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error)
|
||||
GetReservations(ctx context.Context) ([]Reservation, error)
|
||||
GetSwapUpdates(ctx context.Context, swapHash []byte) ([]SwapUpdate, error)
|
||||
GetSweepStatus(ctx context.Context, swapHash []byte) (bool, error)
|
||||
GetUnconfirmedBatches(ctx context.Context) ([]SweepBatch, error)
|
||||
InsertBatch(ctx context.Context, arg InsertBatchParams) (int32, error)
|
||||
InsertHtlcKeys(ctx context.Context, arg InsertHtlcKeysParams) error
|
||||
InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error
|
||||
InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error
|
||||
InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error
|
||||
InsertSwap(ctx context.Context, arg InsertSwapParams) error
|
||||
InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error
|
||||
UpdateBatch(ctx context.Context, arg UpdateBatchParams) error
|
||||
UpdateReservation(ctx context.Context, arg UpdateReservationParams) error
|
||||
UpsertLiquidityParams(ctx context.Context, params []byte) error
|
||||
UpsertSweep(ctx context.Context, arg UpsertSweepParams) error
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
|
|
|
|||
91
loopdb/sqlc/queries/batch.sql
Normal file
91
loopdb/sqlc/queries/batch.sql
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
-- name: GetUnconfirmedBatches :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
sweep_batches
|
||||
WHERE
|
||||
confirmed = FALSE;
|
||||
|
||||
-- name: InsertBatch :one
|
||||
INSERT INTO sweep_batches (
|
||||
confirmed,
|
||||
batch_tx_id,
|
||||
batch_pk_script,
|
||||
last_rbf_height,
|
||||
last_rbf_sat_per_kw,
|
||||
max_timeout_distance
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6
|
||||
) RETURNING id;
|
||||
|
||||
-- name: UpdateBatch :exec
|
||||
UPDATE sweep_batches SET
|
||||
confirmed = $2,
|
||||
batch_tx_id = $3,
|
||||
batch_pk_script = $4,
|
||||
last_rbf_height = $5,
|
||||
last_rbf_sat_per_kw = $6
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ConfirmBatch :exec
|
||||
UPDATE
|
||||
sweep_batches
|
||||
SET
|
||||
confirmed = TRUE
|
||||
WHERE
|
||||
id = $1;
|
||||
|
||||
-- name: UpsertSweep :exec
|
||||
INSERT INTO sweeps (
|
||||
swap_hash,
|
||||
batch_id,
|
||||
outpoint_txid,
|
||||
outpoint_index,
|
||||
amt,
|
||||
completed
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6
|
||||
) ON CONFLICT (swap_hash) DO UPDATE SET
|
||||
batch_id = $2,
|
||||
outpoint_txid = $3,
|
||||
outpoint_index = $4,
|
||||
amt = $5,
|
||||
completed = $6;
|
||||
|
||||
|
||||
-- name: GetBatchSweeps :many
|
||||
SELECT
|
||||
sweeps.*,
|
||||
swaps.*,
|
||||
loopout_swaps.*,
|
||||
htlc_keys.*
|
||||
FROM
|
||||
sweeps
|
||||
JOIN
|
||||
swaps ON sweeps.swap_hash = swaps.swap_hash
|
||||
JOIN
|
||||
loopout_swaps ON sweeps.swap_hash = loopout_swaps.swap_hash
|
||||
JOIN
|
||||
htlc_keys ON sweeps.swap_hash = htlc_keys.swap_hash
|
||||
WHERE
|
||||
sweeps.batch_id = $1
|
||||
ORDER BY
|
||||
sweeps.id ASC;
|
||||
|
||||
-- name: GetSweepStatus :one
|
||||
SELECT
|
||||
COALESCE(s.completed, f.false_value) AS completed
|
||||
FROM
|
||||
(SELECT false AS false_value) AS f
|
||||
LEFT JOIN
|
||||
sweeps s ON s.swap_hash = $1;
|
||||
|
|
@ -104,9 +104,10 @@ INSERT INTO loopout_swaps (
|
|||
outgoing_chan_set,
|
||||
prepay_invoice,
|
||||
max_prepay_routing_fee,
|
||||
publication_deadline
|
||||
publication_deadline,
|
||||
single_sweep
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
|
||||
);
|
||||
|
||||
-- name: InsertLoopIn :exec
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ func (q *Queries) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, erro
|
|||
const getLoopOutSwap = `-- name: GetLoopOutSwap :one
|
||||
SELECT
|
||||
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
|
||||
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline,
|
||||
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep,
|
||||
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
|
||||
FROM
|
||||
swaps
|
||||
|
|
@ -203,6 +203,7 @@ type GetLoopOutSwapRow struct {
|
|||
PrepayInvoice string
|
||||
MaxPrepayRoutingFee int64
|
||||
PublicationDeadline time.Time
|
||||
SingleSweep bool
|
||||
SwapHash_3 []byte
|
||||
SenderScriptPubkey []byte
|
||||
ReceiverScriptPubkey []byte
|
||||
|
|
@ -237,6 +238,7 @@ func (q *Queries) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopO
|
|||
&i.PrepayInvoice,
|
||||
&i.MaxPrepayRoutingFee,
|
||||
&i.PublicationDeadline,
|
||||
&i.SingleSweep,
|
||||
&i.SwapHash_3,
|
||||
&i.SenderScriptPubkey,
|
||||
&i.ReceiverScriptPubkey,
|
||||
|
|
@ -251,7 +253,7 @@ func (q *Queries) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopO
|
|||
const getLoopOutSwaps = `-- name: GetLoopOutSwaps :many
|
||||
SELECT
|
||||
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
|
||||
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline,
|
||||
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep,
|
||||
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
|
||||
FROM
|
||||
swaps
|
||||
|
|
@ -285,6 +287,7 @@ type GetLoopOutSwapsRow struct {
|
|||
PrepayInvoice string
|
||||
MaxPrepayRoutingFee int64
|
||||
PublicationDeadline time.Time
|
||||
SingleSweep bool
|
||||
SwapHash_3 []byte
|
||||
SenderScriptPubkey []byte
|
||||
ReceiverScriptPubkey []byte
|
||||
|
|
@ -325,6 +328,7 @@ func (q *Queries) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, er
|
|||
&i.PrepayInvoice,
|
||||
&i.MaxPrepayRoutingFee,
|
||||
&i.PublicationDeadline,
|
||||
&i.SingleSweep,
|
||||
&i.SwapHash_3,
|
||||
&i.SenderScriptPubkey,
|
||||
&i.ReceiverScriptPubkey,
|
||||
|
|
@ -465,9 +469,10 @@ INSERT INTO loopout_swaps (
|
|||
outgoing_chan_set,
|
||||
prepay_invoice,
|
||||
max_prepay_routing_fee,
|
||||
publication_deadline
|
||||
publication_deadline,
|
||||
single_sweep
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -482,6 +487,7 @@ type InsertLoopOutParams struct {
|
|||
PrepayInvoice string
|
||||
MaxPrepayRoutingFee int64
|
||||
PublicationDeadline time.Time
|
||||
SingleSweep bool
|
||||
}
|
||||
|
||||
func (q *Queries) InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error {
|
||||
|
|
@ -496,6 +502,7 @@ func (q *Queries) InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) er
|
|||
arg.PrepayInvoice,
|
||||
arg.MaxPrepayRoutingFee,
|
||||
arg.PublicationDeadline,
|
||||
arg.SingleSweep,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ var (
|
|||
// errInvalidKey is returned when a serialized key is not the expected
|
||||
// length.
|
||||
errInvalidKey = fmt.Errorf("invalid serialized key")
|
||||
|
||||
// errUnimplemented is returned when a method is not implemented.
|
||||
errUnimplemented = fmt.Errorf("unimplemented method")
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -990,19 +993,19 @@ func (s *boltSwapStore) fetchLoopInSwap(rootBucket *bbolt.Bucket,
|
|||
func (b *boltSwapStore) BatchCreateLoopOut(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*LoopOutContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
return errUnimplemented
|
||||
}
|
||||
|
||||
// BatchCreateLoopIn creates a batch of loop in swaps to the store.
|
||||
func (b *boltSwapStore) BatchCreateLoopIn(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*LoopInContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
return errUnimplemented
|
||||
}
|
||||
|
||||
// BatchInsertUpdate inserts batch of swap updates to the store.
|
||||
func (b *boltSwapStore) BatchInsertUpdate(ctx context.Context,
|
||||
updateData map[lntypes.Hash][]BatchInsertUpdateData) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
return errUnimplemented
|
||||
}
|
||||
|
|
|
|||
339
loopdb/store_mock.go
Normal file
339
loopdb/store_mock.go
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
package loopdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// StoreMock implements a mock client swap store.
|
||||
type StoreMock struct {
|
||||
LoopOutSwaps map[lntypes.Hash]*LoopOutContract
|
||||
LoopOutUpdates map[lntypes.Hash][]SwapStateData
|
||||
loopOutStoreChan chan LoopOutContract
|
||||
loopOutUpdateChan chan SwapStateData
|
||||
|
||||
LoopInSwaps map[lntypes.Hash]*LoopInContract
|
||||
LoopInUpdates map[lntypes.Hash][]SwapStateData
|
||||
loopInStoreChan chan LoopInContract
|
||||
loopInUpdateChan chan SwapStateData
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// NewStoreMock instantiates a new mock store.
|
||||
func NewStoreMock(t *testing.T) *StoreMock {
|
||||
return &StoreMock{
|
||||
loopOutStoreChan: make(chan LoopOutContract, 1),
|
||||
loopOutUpdateChan: make(chan SwapStateData, 1),
|
||||
LoopOutSwaps: make(map[lntypes.Hash]*LoopOutContract),
|
||||
LoopOutUpdates: make(map[lntypes.Hash][]SwapStateData),
|
||||
|
||||
loopInStoreChan: make(chan LoopInContract, 1),
|
||||
loopInUpdateChan: make(chan SwapStateData, 1),
|
||||
LoopInSwaps: make(map[lntypes.Hash]*LoopInContract),
|
||||
LoopInUpdates: make(map[lntypes.Hash][]SwapStateData),
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// FetchLoopOutSwaps returns all swaps currently in the store.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut, error) {
|
||||
result := []*LoopOut{}
|
||||
|
||||
for hash, contract := range s.LoopOutSwaps {
|
||||
updates := s.LoopOutUpdates[hash]
|
||||
events := make([]*LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &LoopOut{
|
||||
Loop: Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
result = append(result, swap)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FetchLoopOutSwaps returns all swaps currently in the store.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) FetchLoopOutSwap(ctx context.Context,
|
||||
hash lntypes.Hash) (*LoopOut, error) {
|
||||
|
||||
contract, ok := s.LoopOutSwaps[hash]
|
||||
if !ok {
|
||||
return nil, errors.New("swap not found")
|
||||
}
|
||||
|
||||
updates := s.LoopOutUpdates[hash]
|
||||
events := make([]*LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &LoopOut{
|
||||
Loop: Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
|
||||
return swap, nil
|
||||
}
|
||||
|
||||
// CreateLoopOut adds an initiated swap to the store.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) CreateLoopOut(ctx context.Context, hash lntypes.Hash,
|
||||
swap *LoopOutContract) error {
|
||||
|
||||
_, ok := s.LoopOutSwaps[hash]
|
||||
if ok {
|
||||
return errors.New("swap already exists")
|
||||
}
|
||||
|
||||
s.LoopOutSwaps[hash] = swap
|
||||
s.LoopOutUpdates[hash] = []SwapStateData{}
|
||||
s.loopOutStoreChan <- *swap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchLoopInSwaps returns all in swaps currently in the store.
|
||||
func (s *StoreMock) FetchLoopInSwaps(ctx context.Context) ([]*LoopIn,
|
||||
error) {
|
||||
|
||||
result := []*LoopIn{}
|
||||
|
||||
for hash, contract := range s.LoopInSwaps {
|
||||
updates := s.LoopInUpdates[hash]
|
||||
events := make([]*LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &LoopIn{
|
||||
Loop: Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
result = append(result, swap)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateLoopIn adds an initiated loop in swap to the store.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) CreateLoopIn(ctx context.Context, hash lntypes.Hash,
|
||||
swap *LoopInContract) error {
|
||||
|
||||
_, ok := s.LoopInSwaps[hash]
|
||||
if ok {
|
||||
return errors.New("swap already exists")
|
||||
}
|
||||
|
||||
s.LoopInSwaps[hash] = swap
|
||||
s.LoopInUpdates[hash] = []SwapStateData{}
|
||||
s.loopInStoreChan <- *swap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoopOut stores a new event for a target loop out swap. This appends to
|
||||
// the event log for a particular swap as it goes through the various stages in
|
||||
// its lifetime.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) UpdateLoopOut(ctx context.Context, hash lntypes.Hash,
|
||||
time time.Time, state SwapStateData) error {
|
||||
|
||||
updates, ok := s.LoopOutUpdates[hash]
|
||||
if !ok {
|
||||
return errors.New("swap does not exists")
|
||||
}
|
||||
|
||||
updates = append(updates, state)
|
||||
s.LoopOutUpdates[hash] = updates
|
||||
s.loopOutUpdateChan <- state
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoopIn stores a new event for a target loop in swap. This appends to
|
||||
// the event log for a particular swap as it goes through the various stages in
|
||||
// its lifetime.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) UpdateLoopIn(ctx context.Context, hash lntypes.Hash,
|
||||
time time.Time, state SwapStateData) error {
|
||||
|
||||
updates, ok := s.LoopInUpdates[hash]
|
||||
if !ok {
|
||||
return errors.New("swap does not exists")
|
||||
}
|
||||
|
||||
updates = append(updates, state)
|
||||
s.LoopInUpdates[hash] = updates
|
||||
s.loopInUpdateChan <- state
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutLiquidityParams writes the serialized `manager.Parameters` bytes into the
|
||||
// bucket.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) PutLiquidityParams(ctx context.Context,
|
||||
params []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchLiquidityParams reads the serialized `manager.Parameters` bytes from
|
||||
// the bucket.
|
||||
//
|
||||
// NOTE: Part of the SwapStore interface.
|
||||
func (s *StoreMock) FetchLiquidityParams(ctx context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Close closes the store.
|
||||
func (s *StoreMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDone asserts that the store mock has no pending operations.
|
||||
func (s *StoreMock) IsDone() error {
|
||||
select {
|
||||
case <-s.loopOutStoreChan:
|
||||
return errors.New("storeChan not empty")
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-s.loopOutUpdateChan:
|
||||
return errors.New("updateChan not empty")
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertLoopOutStored asserts that a swap is stored.
|
||||
func (s *StoreMock) AssertLoopOutStored() {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case <-s.loopOutStoreChan:
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
// AssertLoopOutState asserts that a specified state transition is persisted to
|
||||
// disk.
|
||||
func (s *StoreMock) AssertLoopOutState(expectedState SwapState) {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case state := <-s.loopOutUpdateChan:
|
||||
require.Equal(s.t, expectedState, state.State)
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap state to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
// AssertLoopInStored asserts that a loop-in swap is stored.
|
||||
func (s *StoreMock) AssertLoopInStored() {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case <-s.loopInStoreChan:
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
// assertLoopInState asserts that a specified state transition is persisted to
|
||||
// disk.
|
||||
func (s *StoreMock) AssertLoopInState(
|
||||
expectedState SwapState) SwapStateData {
|
||||
|
||||
s.t.Helper()
|
||||
|
||||
state := <-s.loopInUpdateChan
|
||||
require.Equal(s.t, expectedState, state.State)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
// AssertStorePreimageReveal asserts that a swap is marked as preimage revealed.
|
||||
func (s *StoreMock) AssertStorePreimageReveal() {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case state := <-s.loopOutUpdateChan:
|
||||
require.Equal(s.t, StatePreimageRevealed, state.State)
|
||||
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be marked as preimage revealed")
|
||||
}
|
||||
}
|
||||
|
||||
// AssertStoreFinished asserts that a swap is marked as finished.
|
||||
func (s *StoreMock) AssertStoreFinished(expectedResult SwapState) {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case state := <-s.loopOutUpdateChan:
|
||||
require.Equal(s.t, expectedResult, state.State)
|
||||
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be finished")
|
||||
}
|
||||
}
|
||||
|
||||
// BatchCreateLoopOut creates many loop out swaps in a batch.
|
||||
func (b *StoreMock) BatchCreateLoopOut(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*LoopOutContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// BatchCreateLoopIn creates many loop in swaps in a batch.
|
||||
func (b *StoreMock) BatchCreateLoopIn(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*LoopInContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// BatchInsertUpdate inserts many updates for a swap in a batch.
|
||||
func (b *StoreMock) BatchInsertUpdate(ctx context.Context,
|
||||
updateData map[lntypes.Hash][]BatchInsertUpdateData) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/lightninglabs/loop/labels"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
|
|
@ -442,7 +443,7 @@ func validateLoopInContract(height int32, response *newLoopInResponse) error {
|
|||
// initHtlcs creates and updates the native and nested segwit htlcs of the
|
||||
// loopInSwap.
|
||||
func (s *loopInSwap) initHtlcs() error {
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
s.hash, &s.SwapContract, s.swapKit.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
|
|
@ -61,7 +62,7 @@ func testLoopInSuccess(t *testing.T) {
|
|||
|
||||
inSwap := initResult.swap
|
||||
|
||||
ctx.store.assertLoopInStored()
|
||||
ctx.store.AssertLoopInStored()
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
|
|
@ -82,7 +83,7 @@ func testLoopInSuccess(t *testing.T) {
|
|||
require.Nil(t, swapInfo.OutgoingChanSet)
|
||||
|
||||
ctx.assertState(loopdb.StateHtlcPublished)
|
||||
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
|
||||
// Expect htlc to be published.
|
||||
htlcTx := <-ctx.lnd.SendOutputsChannel
|
||||
|
|
@ -95,7 +96,7 @@ func testLoopInSuccess(t *testing.T) {
|
|||
|
||||
// Expect the same state to be written again with the htlc tx hash
|
||||
// and on chain fee.
|
||||
state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
require.NotNil(t, state.HtlcTxHash)
|
||||
require.Equal(t, cost, state.Cost)
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ func testLoopInSuccess(t *testing.T) {
|
|||
|
||||
// Swap is expected to move to the state InvoiceSettled
|
||||
ctx.assertState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
||||
|
||||
// Server spends htlc.
|
||||
successTx := wire.MsgTx{}
|
||||
|
|
@ -138,7 +139,7 @@ func testLoopInSuccess(t *testing.T) {
|
|||
}
|
||||
|
||||
ctx.assertState(loopdb.StateSuccess)
|
||||
ctx.store.assertLoopInState(loopdb.StateSuccess)
|
||||
ctx.store.AssertLoopInState(loopdb.StateSuccess)
|
||||
|
||||
require.NoError(t, <-errChan)
|
||||
}
|
||||
|
|
@ -213,7 +214,7 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
|
|||
require.NoError(t, err)
|
||||
inSwap := initResult.swap
|
||||
|
||||
ctx.store.assertLoopInStored()
|
||||
ctx.store.AssertLoopInStored()
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
|
|
@ -227,7 +228,7 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
|
|||
ctx.assertState(loopdb.StateInitiated)
|
||||
|
||||
ctx.assertState(loopdb.StateHtlcPublished)
|
||||
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
|
||||
var (
|
||||
htlcTx wire.MsgTx
|
||||
|
|
@ -246,7 +247,7 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
|
|||
|
||||
// Expect the same state to be written again with the htlc tx
|
||||
// hash and cost.
|
||||
state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
require.NotNil(t, state.HtlcTxHash)
|
||||
require.Equal(t, cost, state.Cost)
|
||||
} else {
|
||||
|
|
@ -280,7 +281,7 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
|
|||
invalidAmt := externalValue != 0 && externalValue != int64(req.Amount)
|
||||
if invalidAmt {
|
||||
ctx.assertState(loopdb.StateFailIncorrectHtlcAmt)
|
||||
ctx.store.assertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
|
||||
ctx.store.AssertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
|
||||
|
||||
require.NoError(t, <-errChan)
|
||||
return
|
||||
|
|
@ -329,7 +330,7 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
|
|||
ctx.updateInvoiceState(0, invpkg.ContractCanceled)
|
||||
|
||||
ctx.assertState(loopdb.StateFailTimeout)
|
||||
state := ctx.store.assertLoopInState(loopdb.StateFailTimeout)
|
||||
state := ctx.store.AssertLoopInState(loopdb.StateFailTimeout)
|
||||
require.Equal(t, cost, state.Cost)
|
||||
|
||||
require.NoError(t, <-errChan)
|
||||
|
|
@ -449,7 +450,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
|||
pendSwap.Loop.Events[0].Cost = cost
|
||||
}
|
||||
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
testPreimage.Hash(), &contract.SwapContract,
|
||||
cfg.lnd.ChainParams,
|
||||
)
|
||||
|
|
@ -503,7 +504,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
|||
}
|
||||
|
||||
ctx.assertState(loopdb.StateHtlcPublished)
|
||||
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
|
||||
// Expect htlc to be published.
|
||||
htlcTx = <-ctx.lnd.SendOutputsChannel
|
||||
|
|
@ -515,7 +516,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
|||
|
||||
// Expect the same state to be written again with the htlc tx
|
||||
// hash.
|
||||
state := ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
require.NotNil(t, state.HtlcTxHash)
|
||||
} else {
|
||||
ctx.assertState(loopdb.StateHtlcPublished)
|
||||
|
|
@ -547,7 +548,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
|||
|
||||
// Swap is expected to move to the state InvoiceSettled
|
||||
ctx.assertState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
||||
|
||||
// Server spends htlc.
|
||||
successTx := wire.MsgTx{}
|
||||
|
|
@ -566,7 +567,7 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
|||
}
|
||||
|
||||
ctx.assertState(loopdb.StateSuccess)
|
||||
finalState := ctx.store.assertLoopInState(loopdb.StateSuccess)
|
||||
finalState := ctx.store.AssertLoopInState(loopdb.StateSuccess)
|
||||
|
||||
// We expect our server fee to reflect as the difference between htlc
|
||||
// value and invoice amount paid. We use our original on-chain cost, set
|
||||
|
|
@ -597,7 +598,7 @@ func TestAbandonPublishedHtlcState(t *testing.T) {
|
|||
|
||||
// Ensure that the swap is also in the StateFailAbandoned state in the
|
||||
// database.
|
||||
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)
|
||||
ctx.store.AssertLoopInState(loopdb.StateFailAbandoned)
|
||||
|
||||
// Ensure that the swap was abandoned and the execution stopped.
|
||||
err = <-ctx.errChan
|
||||
|
|
@ -668,7 +669,7 @@ func TestAbandonSettledInvoiceState(t *testing.T) {
|
|||
|
||||
// Swap is expected to move to the state InvoiceSettled
|
||||
ctx.assertState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
|
||||
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
||||
|
||||
// The client requests to abandon the published htlc state.
|
||||
inSwap.abandonChan <- struct{}{}
|
||||
|
|
@ -678,7 +679,7 @@ func TestAbandonSettledInvoiceState(t *testing.T) {
|
|||
|
||||
// Ensure that the swap is also in the StateFailAbandoned state in the
|
||||
// database.
|
||||
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)
|
||||
ctx.store.AssertLoopInState(loopdb.StateFailAbandoned)
|
||||
|
||||
// Ensure that the swap was abandoned and the execution stopped.
|
||||
err = <-ctx.errChan
|
||||
|
|
@ -728,14 +729,14 @@ func advanceToPublishedHtlc(t *testing.T, ctx *loopInTestContext) SwapInfo {
|
|||
require.Equal(t, loopdb.StateInitiated, swapInfo.State)
|
||||
|
||||
ctx.assertState(loopdb.StateHtlcPublished)
|
||||
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
|
||||
// Expect htlc to be published.
|
||||
htlcTx := <-ctx.lnd.SendOutputsChannel
|
||||
|
||||
// Expect the same state to be written again with the htlc tx hash
|
||||
// and on chain fee.
|
||||
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
|
||||
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
||||
|
||||
// Expect register for htlc conf (only one, since the htlc is p2tr).
|
||||
<-ctx.lnd.RegisterConfChannel
|
||||
|
|
@ -765,7 +766,7 @@ func startNewLoopIn(t *testing.T, ctx *loopInTestContext, height int32) (
|
|||
|
||||
inSwap := initResult.swap
|
||||
|
||||
ctx.store.assertLoopInStored()
|
||||
ctx.store.AssertLoopInStored()
|
||||
|
||||
go func() {
|
||||
err := inSwap.execute(context.Background(), ctx.cfg, height)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ type loopInTestContext struct {
|
|||
t *testing.T
|
||||
lnd *test.LndMockServices
|
||||
server *serverMock
|
||||
store *storeMock
|
||||
store *loopdb.StoreMock
|
||||
sweeper *sweep.Sweeper
|
||||
cfg *executeConfig
|
||||
statusChan chan SwapInfo
|
||||
|
|
@ -31,7 +31,7 @@ type loopInTestContext struct {
|
|||
func newLoopInTestContext(t *testing.T) *loopInTestContext {
|
||||
lnd := test.NewMockLnd()
|
||||
server := newServerMock(lnd)
|
||||
store := newStoreMock(t)
|
||||
store := loopdb.NewStoreMock(t)
|
||||
sweeper := sweep.Sweeper{Lnd: &lnd.LndServices}
|
||||
|
||||
blockEpochChan := make(chan interface{})
|
||||
|
|
|
|||
594
loopout.go
594
loopout.go
|
|
@ -1,7 +1,6 @@
|
|||
package loop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
|
|
@ -12,22 +11,19 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/labels"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -90,6 +86,7 @@ type loopOutSwap struct {
|
|||
// executeConfig contains extra configuration to execute the swap.
|
||||
type executeConfig struct {
|
||||
sweeper *sweep.Sweeper
|
||||
batcher *sweepbatcher.Batcher
|
||||
statusChan chan<- SwapInfo
|
||||
blockEpochChan <-chan interface{}
|
||||
timerFactory func(time.Duration) <-chan time.Time
|
||||
|
|
@ -172,6 +169,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
contract := loopdb.LoopOutContract{
|
||||
SwapInvoice: swapResp.swapInvoice,
|
||||
DestAddr: request.DestAddr,
|
||||
IsExternalAddr: request.IsExternalAddr,
|
||||
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
|
||||
SweepConfTarget: request.SweepConfTarget,
|
||||
HtlcConfirmations: confs,
|
||||
|
|
@ -206,7 +204,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
swapKit.lastUpdateTime = initiationTime
|
||||
|
||||
// Create the htlc.
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
swapKit.hash, swapKit.contract, swapKit.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -219,7 +217,9 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
|
||||
// Obtain the payment addr since we'll need it later for routing plugin
|
||||
// recommendation and possibly for cancel.
|
||||
paymentAddr, err := obtainSwapPaymentAddr(contract.SwapInvoice, cfg)
|
||||
paymentAddr, err := utils.ObtainSwapPaymentAddr(
|
||||
contract.SwapInvoice, cfg.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -262,7 +262,7 @@ func resumeLoopOutSwap(cfg *swapConfig, pend *loopdb.LoopOut,
|
|||
)
|
||||
|
||||
// Create the htlc.
|
||||
htlc, err := GetHtlc(
|
||||
htlc, err := utils.GetHtlc(
|
||||
swapKit.hash, swapKit.contract, swapKit.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -274,8 +274,8 @@ func resumeLoopOutSwap(cfg *swapConfig, pend *loopdb.LoopOut,
|
|||
|
||||
// Obtain the payment addr since we'll need it later for routing plugin
|
||||
// recommendation and possibly for cancel.
|
||||
paymentAddr, err := obtainSwapPaymentAddr(
|
||||
pend.Contract.SwapInvoice, cfg,
|
||||
paymentAddr, err := utils.ObtainSwapPaymentAddr(
|
||||
pend.Contract.SwapInvoice, cfg.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -301,24 +301,6 @@ func resumeLoopOutSwap(cfg *swapConfig, pend *loopdb.LoopOut,
|
|||
return swap, nil
|
||||
}
|
||||
|
||||
// obtainSwapPaymentAddr will retrieve the payment addr from the passed invoice.
|
||||
func obtainSwapPaymentAddr(swapInvoice string, cfg *swapConfig) (
|
||||
*[32]byte, error) {
|
||||
|
||||
swapPayReq, err := zpay32.Decode(
|
||||
swapInvoice, cfg.lnd.ChainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if swapPayReq.PaymentAddr == nil {
|
||||
return nil, fmt.Errorf("expected payment address for invoice")
|
||||
}
|
||||
|
||||
return swapPayReq.PaymentAddr, nil
|
||||
}
|
||||
|
||||
// sendUpdate reports an update to the swap state.
|
||||
func (s *loopOutSwap) sendUpdate(ctx context.Context) error {
|
||||
info := s.swapInfo()
|
||||
|
|
@ -532,7 +514,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
|
|||
}
|
||||
|
||||
// Try to spend htlc and continue (rbf) until a spend has confirmed.
|
||||
spendDetails, err := s.waitForHtlcSpendConfirmed(
|
||||
spendTx, err := s.waitForHtlcSpendConfirmedV2(
|
||||
globalCtx, *htlcOutpoint, htlcValue,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -541,7 +523,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
|
|||
|
||||
// If spend details are nil, we resolved the swap without waiting for
|
||||
// its spend, so we can exit.
|
||||
if spendDetails == nil {
|
||||
if spendTx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -549,7 +531,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
|
|||
// don't just try to match with the hash of our sweep tx, because it
|
||||
// may be swept by a different (fee) sweep tx from a previous run.
|
||||
htlcInput, err := swap.GetTxInputByOutpoint(
|
||||
spendDetails.SpendingTx, htlcOutpoint,
|
||||
spendTx, htlcOutpoint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -560,7 +542,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
|
|||
s.cost.Server -= htlcValue
|
||||
|
||||
s.cost.Onchain = htlcValue -
|
||||
btcutil.Amount(spendDetails.SpendingTx.TxOut[0].Value)
|
||||
btcutil.Amount(spendTx.TxOut[0].Value)
|
||||
|
||||
s.state = loopdb.StateSuccess
|
||||
} else {
|
||||
|
|
@ -1019,26 +1001,36 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
|
|||
return txConf, nil
|
||||
}
|
||||
|
||||
// waitForHtlcSpendConfirmed waits for the htlc to be spent either by our own
|
||||
// sweep or a server revocation tx. During this process, this function will try
|
||||
// to spend the htlc every block by calling spendFunc.
|
||||
//
|
||||
// TODO: Improve retry/fee increase mechanism. Once in the mempool, server can
|
||||
// sweep offchain. So we must make sure we sweep successfully before on-chain
|
||||
// timeout.
|
||||
func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
||||
// waitForHtlcSpendConfirmedV2 waits for the htlc to be spent either by our own
|
||||
// sweep or a server revocation tx.
|
||||
func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
|
||||
htlcOutpoint wire.OutPoint, htlcValue btcutil.Amount) (
|
||||
*chainntnfs.SpendDetail, error) {
|
||||
*wire.MsgTx, error) {
|
||||
|
||||
spendChan := make(chan *wire.MsgTx)
|
||||
spendErrChan := make(chan error, 1)
|
||||
quitChan := make(chan bool, 1)
|
||||
|
||||
defer func() {
|
||||
quitChan <- true
|
||||
}()
|
||||
|
||||
notifier := sweepbatcher.SpendNotifier{
|
||||
SpendChan: spendChan,
|
||||
SpendErrChan: spendErrChan,
|
||||
QuitChan: quitChan,
|
||||
}
|
||||
|
||||
sweepReq := sweepbatcher.SweepRequest{
|
||||
SwapHash: s.hash,
|
||||
Outpoint: htlcOutpoint,
|
||||
Value: htlcValue,
|
||||
Notifier: ¬ifier,
|
||||
}
|
||||
|
||||
// Register the htlc spend notification.
|
||||
ctx, cancel := context.WithCancel(globalCtx)
|
||||
defer cancel()
|
||||
spendChan, spendErr, err := s.lnd.ChainNotifier.RegisterSpendNtfn(
|
||||
ctx, &htlcOutpoint, s.htlc.PkScript, s.InitiationHeight,
|
||||
)
|
||||
if err != nil {
|
||||
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
|
||||
|
|
@ -1055,26 +1047,20 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
|||
// is used to decide whether we need to push our preimage to
|
||||
// the server.
|
||||
paymentComplete bool
|
||||
// musigSweepTryCount tracts the number of cooperative, MuSig2
|
||||
// sweep attempts.
|
||||
musigSweepTryCount int
|
||||
// musigSweepSuccess tracks whether at least one MuSig2 sweep
|
||||
// txn was successfully published to the mempool.
|
||||
musigSweepSuccess bool
|
||||
)
|
||||
|
||||
timerChan := s.timerFactory(republishDelay)
|
||||
timerChan := s.timerFactory(repushDelay)
|
||||
|
||||
for {
|
||||
select {
|
||||
// Htlc spend, break loop.
|
||||
case spendDetails := <-spendChan:
|
||||
s.log.Infof("Htlc spend by tx: %v",
|
||||
spendDetails.SpenderTxHash)
|
||||
case spendTx := <-spendChan:
|
||||
s.log.Infof("Htlc spend by tx: %v", spendTx.TxHash())
|
||||
|
||||
return spendDetails, nil
|
||||
return spendTx, nil
|
||||
|
||||
// Spend notification error.
|
||||
case err := <-spendErr:
|
||||
case err := <-spendErrChan:
|
||||
return nil, err
|
||||
|
||||
// Receive status updates for our payment so that we can detect
|
||||
|
|
@ -1116,121 +1102,51 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// New block arrived, update height and restart the republish
|
||||
// timer.
|
||||
// New block arrived, update height and try pushing preimage.
|
||||
case notification := <-s.blockEpochChan:
|
||||
s.height = notification.(int32)
|
||||
timerChan = s.timerFactory(republishDelay)
|
||||
timerChan = s.timerFactory(repushDelay)
|
||||
|
||||
// Some time after start or after arrival of a new block, try
|
||||
// to spend again.
|
||||
case <-timerChan:
|
||||
if IsTaprootSwap(&s.SwapContract) {
|
||||
// sweepConfTarget will return false if the
|
||||
// preimage is not revealed yet but the conf
|
||||
// target is closer than 20 blocks. In this case
|
||||
// to be sure we won't attempt to sweep at all
|
||||
// and we won't reveal the preimage either.
|
||||
_, canSweep := s.sweepConfTarget()
|
||||
if !canSweep {
|
||||
s.log.Infof("Aborting swap, timed " +
|
||||
"out on-chain")
|
||||
// sweepConfTarget will return false if the preimage is
|
||||
// not revealed yet but the conf target is closer than
|
||||
// 20 blocks. In this case to be sure we won't attempt
|
||||
// to sweep at all and we won't reveal the preimage
|
||||
// either.
|
||||
_, canSweep := s.sweepConfTarget()
|
||||
if !canSweep {
|
||||
s.log.Infof("Aborting swap, timed " +
|
||||
"out on-chain")
|
||||
|
||||
s.state = loopdb.StateFailTimeout
|
||||
err := s.persistState(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("unable to persist " +
|
||||
"state")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// When using taproot HTLCs we're pushing the
|
||||
// preimage before attempting to sweep. This
|
||||
// way the server will know that the swap will
|
||||
// go through and we'll be able to MuSig2
|
||||
// cosign our sweep transaction. In the worst
|
||||
// case if the server is uncooperative for any
|
||||
// reason we can still sweep using scriptpath
|
||||
// spend.
|
||||
err = s.setStatePreimageRevealed(ctx)
|
||||
s.state = loopdb.StateFailTimeout
|
||||
err := s.persistState(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warnf("unable to persist " +
|
||||
"state")
|
||||
}
|
||||
|
||||
if !paymentComplete {
|
||||
// Push the preimage for as long as the
|
||||
// server is able to settle the swap
|
||||
// invoice. So that we can continue
|
||||
// with the MuSig2 sweep afterwards.
|
||||
s.pushPreimage(ctx)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Now attempt to publish a MuSig2 sweep txn.
|
||||
// Only attempt at most maxMusigSweepRetires
|
||||
// times to still leave time for an emergency
|
||||
// script path sweep.
|
||||
if musigSweepTryCount < maxMusigSweepRetries {
|
||||
success := s.sweepMuSig2(
|
||||
ctx, htlcOutpoint, htlcValue,
|
||||
)
|
||||
if !success {
|
||||
musigSweepTryCount++
|
||||
} else {
|
||||
// Mark that we had a sweep
|
||||
// that was successful. There's
|
||||
// no need for the script spend
|
||||
// now we can just keep pushing
|
||||
// new sweeps to bump the fee.
|
||||
musigSweepSuccess = true
|
||||
}
|
||||
} else if !musigSweepSuccess {
|
||||
// Attempt to script path sweep. If the
|
||||
// sweep fails, we can't do any better
|
||||
// than go on and try again later as
|
||||
// the preimage is already revealed and
|
||||
// the server settled the swap payment.
|
||||
// From the server's point of view the
|
||||
// swap is succeeded at this point so
|
||||
// we are free to retry as long as we
|
||||
// want.
|
||||
err := s.sweep(
|
||||
ctx, htlcOutpoint, htlcValue,
|
||||
)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to publish "+
|
||||
"non-cooperative "+
|
||||
"sweep: %v", err)
|
||||
}
|
||||
}
|
||||
// Send the sweep to the sweeper.
|
||||
err := s.batcher.AddSweep(&sweepReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the result of our spend func was that the
|
||||
// swap has reached a final state, then we
|
||||
// return nil spend details, because there is
|
||||
// no further action required for this swap.
|
||||
if s.state.Type() != loopdb.StateTypePending {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
err := s.sweep(ctx, htlcOutpoint, htlcValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Now that the sweep is taken care of, we can update
|
||||
// our state.
|
||||
err = s.setStatePreimageRevealed(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the result of our spend func was that the
|
||||
// swap has reached a final state, then we
|
||||
// return nil spend details, because there is no
|
||||
// further action required for this swap.
|
||||
if s.state.Type() != loopdb.StateTypePending {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If our off chain payment is not yet complete,
|
||||
// we try to push our preimage to the server.
|
||||
if !paymentComplete {
|
||||
s.pushPreimage(ctx)
|
||||
}
|
||||
if !paymentComplete {
|
||||
// Push the preimage for as long as the
|
||||
// server is able to settle the swap
|
||||
// invoice. So that we can continue
|
||||
// with the MuSig2 sweep afterwards.
|
||||
s.pushPreimage(ctx)
|
||||
}
|
||||
|
||||
// Context canceled.
|
||||
|
|
@ -1339,248 +1255,6 @@ func (s *loopOutSwap) failOffChain(ctx context.Context, paymentType paymentType,
|
|||
}
|
||||
}
|
||||
|
||||
// createMuSig2SweepTxn creates a taproot keyspend sweep transaction and
|
||||
// attempts to cooperate with the server to create a MuSig2 signature witness.
|
||||
func (s *loopOutSwap) createMuSig2SweepTxn(
|
||||
ctx context.Context, htlcOutpoint wire.OutPoint,
|
||||
htlcValue btcutil.Amount, fee btcutil.Amount) (*wire.MsgTx, error) {
|
||||
|
||||
// First assemble our taproot keyspend sweep transaction and get the
|
||||
// sig hash.
|
||||
sweepTx, sweepTxPsbt, sigHash, err := s.sweeper.CreateUnsignedTaprootKeySpendSweepTx(
|
||||
ctx, uint32(s.height), s.htlc, htlcOutpoint, htlcValue, fee,
|
||||
s.DestAddr,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
signers [][]byte
|
||||
muSig2Version input.MuSig2Version
|
||||
)
|
||||
|
||||
// Depending on the MuSig2 version we either pass 32 byte Schnorr
|
||||
// public keys or normal 33 byte public keys.
|
||||
if s.ProtocolVersion >= loopdb.ProtocolVersionMuSig2 {
|
||||
muSig2Version = input.MuSig2Version100RC2
|
||||
signers = [][]byte{
|
||||
s.HtlcKeys.SenderInternalPubKey[:],
|
||||
s.HtlcKeys.ReceiverInternalPubKey[:],
|
||||
}
|
||||
} else {
|
||||
muSig2Version = input.MuSig2Version040
|
||||
signers = [][]byte{
|
||||
s.HtlcKeys.SenderInternalPubKey[1:],
|
||||
s.HtlcKeys.ReceiverInternalPubKey[1:],
|
||||
}
|
||||
}
|
||||
|
||||
htlcScript, ok := s.htlc.HtlcScript.(*swap.HtlcScriptV3)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("non taproot htlc")
|
||||
}
|
||||
|
||||
// Now we're creating a local MuSig2 session using the receiver key's
|
||||
// key locator and the htlc's root hash.
|
||||
musig2SessionInfo, err := s.lnd.Signer.MuSig2CreateSession(
|
||||
ctx, muSig2Version, &s.HtlcKeys.ClientScriptKeyLocator, signers,
|
||||
lndclient.MuSig2TaprootTweakOpt(htlcScript.RootHash[:], false),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the session active, we can now send the server our public nonce
|
||||
// and the sig hash, so that it can create it's own MuSig2 session and
|
||||
// return the server side nonce and partial signature.
|
||||
serverNonce, serverSig, err := s.swapKit.server.MuSig2SignSweep(
|
||||
ctx, s.SwapContract.ProtocolVersion, s.hash,
|
||||
s.swapInvoicePaymentAddr, musig2SessionInfo.PublicNonce[:],
|
||||
sweepTxPsbt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serverPublicNonce [musig2.PubNonceSize]byte
|
||||
copy(serverPublicNonce[:], serverNonce)
|
||||
|
||||
// Register the server's nonce before attempting to create our partial
|
||||
// signature.
|
||||
haveAllNonces, err := s.lnd.Signer.MuSig2RegisterNonces(
|
||||
ctx, musig2SessionInfo.SessionID,
|
||||
[][musig2.PubNonceSize]byte{serverPublicNonce},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanity check that we have all the nonces.
|
||||
if !haveAllNonces {
|
||||
return nil, fmt.Errorf("invalid MuSig2 session: nonces missing")
|
||||
}
|
||||
|
||||
var digest [32]byte
|
||||
copy(digest[:], sigHash)
|
||||
|
||||
// Since our MuSig2 session has all nonces, we can now create the local
|
||||
// partial signature by signing the sig hash.
|
||||
_, err = s.lnd.Signer.MuSig2Sign(
|
||||
ctx, musig2SessionInfo.SessionID, digest, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now combine the partial signatures to use the final combined
|
||||
// signature in the sweep transaction's witness.
|
||||
haveAllSigs, finalSig, err := s.lnd.Signer.MuSig2CombineSig(
|
||||
ctx, musig2SessionInfo.SessionID, [][]byte{serverSig},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !haveAllSigs {
|
||||
return nil, fmt.Errorf("failed to combine signatures")
|
||||
}
|
||||
|
||||
// To be sure that we're good, parse and validate that the combined
|
||||
// signature is indeed valid for the sig hash and the internal pubkey.
|
||||
err = s.executeConfig.verifySchnorrSig(
|
||||
htlcScript.TaprootKey, sigHash, finalSig,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that we know the signature is correct, we can fill it in to our
|
||||
// witness.
|
||||
sweepTx.TxIn[0].Witness = wire.TxWitness{
|
||||
finalSig,
|
||||
}
|
||||
|
||||
return sweepTx, nil
|
||||
}
|
||||
|
||||
// sweepConfTarget returns the confirmation target for the htlc sweep or false
|
||||
// if we're too late.
|
||||
func (s *loopOutSwap) sweepConfTarget() (int32, bool) {
|
||||
remainingBlocks := s.CltvExpiry - s.height
|
||||
blocksToLastReveal := remainingBlocks - MinLoopOutPreimageRevealDelta
|
||||
preimageRevealed := s.state == loopdb.StatePreimageRevealed
|
||||
|
||||
// If we have not revealed our preimage, and we don't have time left
|
||||
// to sweep the swap, we abandon the swap because we can no longer
|
||||
// sweep the success path (without potentially having to compete with
|
||||
// the server's timeout sweep), and we have not had any coins pulled
|
||||
// off-chain.
|
||||
if blocksToLastReveal <= 0 && !preimageRevealed {
|
||||
s.log.Infof("Preimage can no longer be safely revealed: "+
|
||||
"expires at: %v, current height: %v", s.CltvExpiry,
|
||||
s.height)
|
||||
|
||||
s.state = loopdb.StateFailTimeout
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Calculate the transaction fee based on the confirmation target
|
||||
// required to sweep the HTLC before the timeout. We'll use the
|
||||
// confirmation target provided by the client unless we've come too
|
||||
// close to the expiration height, in which case we'll use the default
|
||||
// if it is better than what the client provided.
|
||||
confTarget := s.SweepConfTarget
|
||||
if remainingBlocks <= DefaultSweepConfTargetDelta &&
|
||||
confTarget > DefaultSweepConfTarget {
|
||||
|
||||
confTarget = DefaultSweepConfTarget
|
||||
}
|
||||
|
||||
return confTarget, true
|
||||
}
|
||||
|
||||
// clampSweepFee will clamp the passed in sweep fee to the maximum configured
|
||||
// miner fee. Returns false if sweeping should not continue. Note that in the
|
||||
// MuSig2 case we always continue as the preimage is revealed to the server
|
||||
// before cooperatively signing the sweep transaction.
|
||||
func (s *loopOutSwap) clampSweepFee(fee btcutil.Amount) (btcutil.Amount, bool) {
|
||||
// Ensure it doesn't exceed our maximum fee allowed.
|
||||
if fee > s.MaxMinerFee {
|
||||
s.log.Warnf("Required fee %v exceeds max miner fee of %v",
|
||||
fee, s.MaxMinerFee)
|
||||
|
||||
if s.state == loopdb.StatePreimageRevealed {
|
||||
// The currently required fee exceeds the max, but we
|
||||
// already revealed the preimage. The best we can do now
|
||||
// is to republish with the max fee.
|
||||
fee = s.MaxMinerFee
|
||||
} else {
|
||||
s.log.Warnf("Not revealing preimage")
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
return fee, true
|
||||
}
|
||||
|
||||
// sweepMuSig2 attempts to sweep the on-chain HTLC using MuSig2. If anything
|
||||
// fails, we'll log it but will simply return to allow further retries. Since
|
||||
// the preimage is revealed by the time we attempt to MuSig2 sweep, we'll need
|
||||
// to fall back to a script spend sweep if all MuSig2 sweep attempts fail (for
|
||||
// example the server could be down due to maintenance or any other issue
|
||||
// making the cooperative sweep fail).
|
||||
func (s *loopOutSwap) sweepMuSig2(ctx context.Context,
|
||||
htlcOutpoint wire.OutPoint, htlcValue btcutil.Amount) bool {
|
||||
|
||||
addInputToEstimator := func(e *input.TxWeightEstimator) error {
|
||||
e.AddTaprootKeySpendInput(txscript.SigHashDefault)
|
||||
return nil
|
||||
}
|
||||
|
||||
confTarget, _ := s.sweepConfTarget()
|
||||
fee, err := s.sweeper.GetSweepFee(
|
||||
ctx, addInputToEstimator, s.DestAddr, confTarget,
|
||||
)
|
||||
if err != nil {
|
||||
s.log.Warnf("Failed to estimate fee MuSig2 sweep txn: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
fee, _ = s.clampSweepFee(fee)
|
||||
|
||||
// Now attempt the co-signing of the txn.
|
||||
sweepTx, err := s.createMuSig2SweepTxn(
|
||||
ctx, htlcOutpoint, htlcValue, fee,
|
||||
)
|
||||
if err != nil {
|
||||
s.log.Warnf("Failed to create MuSig2 sweep txn: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Finally, try publish the txn.
|
||||
s.log.Infof("Sweep on chain HTLC using MuSig2 to address %v "+
|
||||
"fee %v (tx %v)", s.DestAddr, fee, sweepTx.TxHash())
|
||||
|
||||
err = s.lnd.WalletKit.PublishTransaction(
|
||||
ctx, sweepTx,
|
||||
labels.LoopOutSweepSuccess(swap.ShortHash(&s.hash)),
|
||||
)
|
||||
if err != nil {
|
||||
var sweepTxBuf bytes.Buffer
|
||||
if err := sweepTx.Serialize(&sweepTxBuf); err != nil {
|
||||
s.log.Warnf("Unable to serialize sweep txn: %v", err)
|
||||
}
|
||||
|
||||
s.log.Warnf("Publish of MuSig2 sweep failed: %v. Raw tx: %x",
|
||||
err, sweepTxBuf.Bytes())
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *loopOutSwap) setStatePreimageRevealed(ctx context.Context) error {
|
||||
if s.state != loopdb.StatePreimageRevealed {
|
||||
s.state = loopdb.StatePreimageRevealed
|
||||
|
|
@ -1594,78 +1268,6 @@ func (s *loopOutSwap) setStatePreimageRevealed(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// sweep tries to sweep the given htlc to a destination address. It takes into
|
||||
// account the max miner fee and unless the preimage is already revealed
|
||||
// (MuSig2 case), marks the preimage as revealed when it published the tx. If
|
||||
// the preimage has not yet been revealed, and the time during which we can
|
||||
// safely reveal it has passed, the swap will be marked as failed, and the
|
||||
// function will return.
|
||||
func (s *loopOutSwap) sweep(ctx context.Context, htlcOutpoint wire.OutPoint,
|
||||
htlcValue btcutil.Amount) error {
|
||||
|
||||
confTarget, canSweep := s.sweepConfTarget()
|
||||
if !canSweep {
|
||||
return nil
|
||||
}
|
||||
|
||||
fee, err := s.sweeper.GetSweepFee(
|
||||
ctx, s.htlc.AddSuccessToEstimator, s.DestAddr, confTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fee, canSweep = s.clampSweepFee(fee)
|
||||
if !canSweep {
|
||||
return nil
|
||||
}
|
||||
|
||||
witnessFunc := func(sig []byte) (wire.TxWitness, error) {
|
||||
return s.htlc.GenSuccessWitness(sig, s.Preimage)
|
||||
}
|
||||
|
||||
// Retrieve the full script required to unlock the output.
|
||||
redeemScript := s.htlc.SuccessScript()
|
||||
|
||||
// Create sweep tx.
|
||||
sweepTx, err := s.sweeper.CreateSweepTx(
|
||||
ctx, s.height, s.htlc.SuccessSequence(), s.htlc,
|
||||
htlcOutpoint, s.contract.HtlcKeys.ReceiverScriptKey,
|
||||
redeemScript, witnessFunc, htlcValue, fee, s.DestAddr,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before publishing the tx, already mark the preimage as revealed. This
|
||||
// is a precaution in case the publish call never returns and would
|
||||
// leave us thinking we didn't reveal yet.
|
||||
err = s.setStatePreimageRevealed(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Publish tx.
|
||||
s.log.Infof("Sweep on chain HTLC to address %v with fee %v (tx %v)",
|
||||
s.DestAddr, fee, sweepTx.TxHash())
|
||||
|
||||
err = s.lnd.WalletKit.PublishTransaction(
|
||||
ctx, sweepTx,
|
||||
labels.LoopOutSweepSuccess(swap.ShortHash(&s.hash)),
|
||||
)
|
||||
if err != nil {
|
||||
var sweepTxBuf bytes.Buffer
|
||||
if err := sweepTx.Serialize(&sweepTxBuf); err != nil {
|
||||
s.log.Warnf("Unable to serialize sweep txn: %v", err)
|
||||
}
|
||||
|
||||
s.log.Warnf("Publish sweep failed: %v. Raw tx: %x",
|
||||
err, sweepTxBuf.Bytes())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLoopOutContract validates the contract parameters against our
|
||||
// request.
|
||||
func validateLoopOutContract(lnd *lndclient.LndServices, request *OutRequest,
|
||||
|
|
@ -1711,3 +1313,39 @@ func validateLoopOutContract(lnd *lndclient.LndServices, request *OutRequest,
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sweepConfTarget returns the confirmation target for the htlc sweep or false
|
||||
// if we're too late.
|
||||
func (s *loopOutSwap) sweepConfTarget() (int32, bool) {
|
||||
remainingBlocks := s.CltvExpiry - s.height
|
||||
blocksToLastReveal := remainingBlocks - MinLoopOutPreimageRevealDelta
|
||||
preimageRevealed := s.state == loopdb.StatePreimageRevealed
|
||||
|
||||
// If we have not revealed our preimage, and we don't have time left
|
||||
// to sweep the swap, we abandon the swap because we can no longer
|
||||
// sweep the success path (without potentially having to compete with
|
||||
// the server's timeout sweep), and we have not had any coins pulled
|
||||
// off-chain.
|
||||
if blocksToLastReveal <= 0 && !preimageRevealed {
|
||||
s.log.Infof("Preimage can no longer be safely revealed: "+
|
||||
"expires at: %v, current height: %v", s.CltvExpiry,
|
||||
s.height)
|
||||
|
||||
s.state = loopdb.StateFailTimeout
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Calculate the transaction fee based on the confirmation target
|
||||
// required to sweep the HTLC before the timeout. We'll use the
|
||||
// confirmation target provided by the client unless we've come too
|
||||
// close to the expiration height, in which case we'll use the default
|
||||
// if it is better than what the client provided.
|
||||
confTarget := s.SweepConfTarget
|
||||
if remainingBlocks <= DefaultSweepConfTargetDelta &&
|
||||
confTarget > DefaultSweepConfTarget {
|
||||
|
||||
confTarget = DefaultSweepConfTarget
|
||||
}
|
||||
|
||||
return confTarget, true
|
||||
}
|
||||
|
|
|
|||
206
loopout_test.go
206
loopout_test.go
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
|
|
@ -45,7 +46,7 @@ func testLoopOutPaymentParameters(t *testing.T) {
|
|||
lnd := test.NewMockLnd()
|
||||
ctx := test.NewContext(t, lnd)
|
||||
server := newServerMock(lnd)
|
||||
store := newStoreMock(t)
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
expiryChan := make(chan time.Time)
|
||||
timerFactory := func(_ time.Duration) <-chan time.Time {
|
||||
|
|
@ -99,7 +100,7 @@ func testLoopOutPaymentParameters(t *testing.T) {
|
|||
errChan <- err
|
||||
}()
|
||||
|
||||
store.assertLoopOutStored()
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
state := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, state.State)
|
||||
|
|
@ -168,7 +169,7 @@ func testLateHtlcPublish(t *testing.T) {
|
|||
|
||||
server := newServerMock(lnd)
|
||||
|
||||
store := newStoreMock(t)
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
expiryChan := make(chan time.Time)
|
||||
timerFactory := func(expiry time.Duration) <-chan time.Time {
|
||||
|
|
@ -208,7 +209,7 @@ func testLateHtlcPublish(t *testing.T) {
|
|||
errChan <- err
|
||||
}()
|
||||
|
||||
store.assertLoopOutStored()
|
||||
store.AssertLoopOutStored()
|
||||
status := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, status.State)
|
||||
|
||||
|
|
@ -228,7 +229,7 @@ func testLateHtlcPublish(t *testing.T) {
|
|||
errors.New(lndclient.PaymentResultUnknownPaymentHash),
|
||||
)
|
||||
|
||||
store.assertStoreFinished(loopdb.StateFailTimeout)
|
||||
store.AssertStoreFinished(loopdb.StateFailTimeout)
|
||||
|
||||
status = <-statusChan
|
||||
require.Equal(t, loopdb.StateFailTimeout, status.State)
|
||||
|
|
@ -273,7 +274,7 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 10000)
|
||||
|
||||
cfg := newSwapConfig(
|
||||
&lnd.LndServices, newStoreMock(t), server,
|
||||
&lnd.LndServices, loopdb.NewStoreMock(t), server,
|
||||
)
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
|
|
@ -293,13 +294,33 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
return expiryChan
|
||||
}
|
||||
|
||||
errChan := make(chan error)
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
batcherStore := sweepbatcher.NewStoreMock()
|
||||
|
||||
batcher := sweepbatcher.NewBatcher(
|
||||
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
|
||||
lnd.ChainParams, batcherStore, cfg.store,
|
||||
)
|
||||
|
||||
tctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
err := swap.execute(context.Background(), &executeConfig{
|
||||
err := batcher.Run(tctx)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := swap.execute(tctx, &executeConfig{
|
||||
statusChan: statusChan,
|
||||
blockEpochChan: blockEpochChan,
|
||||
timerFactory: timerFactory,
|
||||
sweeper: sweeper,
|
||||
batcher: batcher,
|
||||
cancelSwap: server.CancelLoopOutSwap,
|
||||
verifySchnorrSig: mockVerifySchnorrSigFail,
|
||||
}, ctx.Lnd.Height)
|
||||
|
|
@ -310,7 +331,7 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
}()
|
||||
|
||||
// The swap should be found in its initial state.
|
||||
cfg.store.(*storeMock).assertLoopOutStored()
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutStored()
|
||||
state := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, state.State)
|
||||
|
||||
|
|
@ -335,22 +356,27 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
|
||||
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()
|
||||
|
||||
expiryChan <- time.Now()
|
||||
|
||||
// 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)
|
||||
|
||||
ctx.AssertEpochListeners(1)
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect a signing request for the HTLC success transaction.
|
||||
if !IsTaprootSwap(&swap.SwapContract) {
|
||||
<-ctx.Lnd.SignOutputRawChannel
|
||||
}
|
||||
|
||||
cfg.store.(*storeMock).assertLoopOutState(loopdb.StatePreimageRevealed)
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(loopdb.StatePreimageRevealed)
|
||||
status := <-statusChan
|
||||
require.Equal(t, loopdb.StatePreimageRevealed, status.State)
|
||||
|
||||
|
|
@ -409,7 +435,7 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
|
||||
// The sweep should have a fee that corresponds to the custom
|
||||
// confirmation target.
|
||||
_ = assertSweepTx(testReq.SweepConfTarget)
|
||||
sweepTx := assertSweepTx(testReq.SweepConfTarget)
|
||||
|
||||
// Once we have published an on chain sweep, we expect a preimage to
|
||||
// have been pushed to our server.
|
||||
|
|
@ -426,24 +452,14 @@ func testCustomSweepConfTarget(t *testing.T) {
|
|||
State: lnrpc.Payment_SUCCEEDED,
|
||||
}
|
||||
|
||||
// We'll then notify the height at which we begin using the default
|
||||
// confirmation target.
|
||||
defaultConfTargetHeight := ctx.Lnd.Height +
|
||||
testLoopOutMinOnChainCltvDelta - DefaultSweepConfTargetDelta
|
||||
blockEpochChan <- defaultConfTargetHeight
|
||||
expiryChan <- time.Now()
|
||||
|
||||
// Expect another signing request.
|
||||
<-ctx.Lnd.SignOutputRawChannel
|
||||
|
||||
// We should expect to see another sweep using the higher fee since the
|
||||
// spend hasn't been confirmed yet.
|
||||
sweepTx := assertSweepTx(DefaultSweepConfTarget)
|
||||
|
||||
// Notify the spend so that the swap reaches its final state.
|
||||
// Notify the batch for the spend.
|
||||
ctx.NotifySpend(sweepTx, 0)
|
||||
|
||||
cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
|
||||
// After receiving the notification the batch will start monitoring the
|
||||
// confirmations.
|
||||
ctx.AssertRegisterConf(true, 3)
|
||||
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(loopdb.StateSuccess)
|
||||
status = <-statusChan
|
||||
require.Equal(t, loopdb.StateSuccess, status.State)
|
||||
require.NoError(t, <-errChan)
|
||||
|
|
@ -493,7 +509,7 @@ func testPreimagePush(t *testing.T) {
|
|||
)
|
||||
|
||||
cfg := newSwapConfig(
|
||||
&lnd.LndServices, newStoreMock(t), server,
|
||||
&lnd.LndServices, loopdb.NewStoreMock(t), server,
|
||||
)
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
|
|
@ -511,13 +527,33 @@ func testPreimagePush(t *testing.T) {
|
|||
return expiryChan
|
||||
}
|
||||
|
||||
errChan := make(chan error)
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
batcherStore := sweepbatcher.NewStoreMock()
|
||||
|
||||
batcher := sweepbatcher.NewBatcher(
|
||||
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
|
||||
lnd.ChainParams, batcherStore, cfg.store,
|
||||
)
|
||||
|
||||
tctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
err := batcher.Run(tctx)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := swap.execute(context.Background(), &executeConfig{
|
||||
statusChan: statusChan,
|
||||
blockEpochChan: blockEpochChan,
|
||||
timerFactory: timerFactory,
|
||||
sweeper: sweeper,
|
||||
batcher: batcher,
|
||||
cancelSwap: server.CancelLoopOutSwap,
|
||||
verifySchnorrSig: mockVerifySchnorrSigFail,
|
||||
}, ctx.Lnd.Height)
|
||||
|
|
@ -528,7 +564,7 @@ func testPreimagePush(t *testing.T) {
|
|||
}()
|
||||
|
||||
// The swap should be found in its initial state.
|
||||
cfg.store.(*storeMock).assertLoopOutStored()
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutStored()
|
||||
state := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, state.State)
|
||||
|
||||
|
|
@ -553,10 +589,6 @@ func testPreimagePush(t *testing.T) {
|
|||
|
||||
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()
|
||||
|
|
@ -567,11 +599,20 @@ func testPreimagePush(t *testing.T) {
|
|||
// preimage is not revealed, we also do not expect a preimage push.
|
||||
expiryChan <- testTime
|
||||
|
||||
// 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)
|
||||
|
||||
ctx.AssertEpochListeners(1)
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// When using taproot htlcs the flow is different as we do reveal the
|
||||
// preimage before sweeping in order for the server to trust us with
|
||||
// our MuSig2 signing attempts.
|
||||
if IsTaprootSwap(&swap.SwapContract) {
|
||||
cfg.store.(*storeMock).assertLoopOutState(
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(
|
||||
loopdb.StatePreimageRevealed,
|
||||
)
|
||||
status := <-statusChan
|
||||
|
|
@ -582,15 +623,6 @@ func testPreimagePush(t *testing.T) {
|
|||
preimage := <-server.preimagePush
|
||||
require.Equal(t, swap.Preimage, preimage)
|
||||
|
||||
// Try MuSig2 signing first and fail it so that we go for a
|
||||
// normal sweep.
|
||||
for i := 0; i < maxMusigSweepRetries; i++ {
|
||||
expiryChan <- time.Now()
|
||||
|
||||
preimage := <-server.preimagePush
|
||||
require.Equal(t, swap.Preimage, preimage)
|
||||
}
|
||||
|
||||
<-ctx.Lnd.SignOutputRawChannel
|
||||
|
||||
// We expect the sweep tx to have been published.
|
||||
|
|
@ -611,6 +643,10 @@ func testPreimagePush(t *testing.T) {
|
|||
// Now when we report a new block and tick our expiry fee timer, and
|
||||
// fees are acceptably low so we expect our sweep to be published.
|
||||
blockEpochChan <- ctx.Lnd.Height + 2
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
expiryChan <- testTime
|
||||
|
||||
if IsTaprootSwap(&swap.SwapContract) {
|
||||
|
|
@ -624,7 +660,7 @@ func testPreimagePush(t *testing.T) {
|
|||
if !IsTaprootSwap(&swap.SwapContract) {
|
||||
// This is the first time we have swept, so we expect our
|
||||
// preimage revealed state to be set.
|
||||
cfg.store.(*storeMock).assertLoopOutState(
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(
|
||||
loopdb.StatePreimageRevealed,
|
||||
)
|
||||
status := <-statusChan
|
||||
|
|
@ -648,6 +684,10 @@ func testPreimagePush(t *testing.T) {
|
|||
// chain yet so we can test our preimage push retry logic. Instead, we
|
||||
// tick the expiry chan again to prompt another sweep.
|
||||
expiryChan <- testTime
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
if IsTaprootSwap(&swap.SwapContract) {
|
||||
preimage := <-server.preimagePush
|
||||
require.Equal(t, swap.Preimage, preimage)
|
||||
|
|
@ -678,6 +718,10 @@ func testPreimagePush(t *testing.T) {
|
|||
// push. The test's mocked preimage channel is un-buffered, so our test
|
||||
// would hang if we pushed the preimage here.
|
||||
expiryChan <- testTime
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
<-ctx.Lnd.SignOutputRawChannel
|
||||
sweepTx := ctx.ReceiveTx()
|
||||
|
||||
|
|
@ -685,7 +729,11 @@ func testPreimagePush(t *testing.T) {
|
|||
// spend our sweepTx and assert that the swap succeeds.
|
||||
ctx.NotifySpend(sweepTx, 0)
|
||||
|
||||
cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
|
||||
// After receiving the spend ntfn the batch will start monitoring for
|
||||
// confs.
|
||||
ctx.AssertRegisterConf(true, 3)
|
||||
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(loopdb.StateSuccess)
|
||||
status := <-statusChan
|
||||
require.Equal(
|
||||
t, status.State, loopdb.StateSuccess,
|
||||
|
|
@ -720,7 +768,7 @@ func testFailedOffChainCancelation(t *testing.T) {
|
|||
testReq.Expiry = lnd.Height + 20
|
||||
|
||||
cfg := newSwapConfig(
|
||||
&lnd.LndServices, newStoreMock(t), server,
|
||||
&lnd.LndServices, loopdb.NewStoreMock(t), server,
|
||||
)
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
|
|
@ -754,7 +802,7 @@ func testFailedOffChainCancelation(t *testing.T) {
|
|||
}()
|
||||
|
||||
// The swap should be found in its initial state.
|
||||
cfg.store.(*storeMock).assertLoopOutStored()
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutStored()
|
||||
state := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, state.State)
|
||||
|
||||
|
|
@ -837,7 +885,7 @@ func testFailedOffChainCancelation(t *testing.T) {
|
|||
server.assertSwapCanceled(t, swapCancelation)
|
||||
|
||||
// Finally, the swap should be recorded with failed off chain timeout.
|
||||
cfg.store.(*storeMock).assertLoopOutState(
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(
|
||||
loopdb.StateFailOffchainPayments,
|
||||
)
|
||||
state = <-statusChan
|
||||
|
|
@ -874,7 +922,7 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
)
|
||||
|
||||
cfg := newSwapConfig(
|
||||
&lnd.LndServices, newStoreMock(t), server,
|
||||
&lnd.LndServices, loopdb.NewStoreMock(t), server,
|
||||
)
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
|
|
@ -892,8 +940,6 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
return expiryChan
|
||||
}
|
||||
|
||||
errChan := make(chan error)
|
||||
|
||||
// Mock a successful signature verify to make sure we don't fail
|
||||
// creating the MuSig2 sweep.
|
||||
mockVerifySchnorrSigSuccess := func(pubKey *btcec.PublicKey, hash,
|
||||
|
|
@ -902,12 +948,33 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
return nil
|
||||
}
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
batcherStore := sweepbatcher.NewStoreMock()
|
||||
|
||||
batcher := sweepbatcher.NewBatcher(
|
||||
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
|
||||
lnd.ChainParams, batcherStore, cfg.store,
|
||||
)
|
||||
|
||||
tctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
err := batcher.Run(tctx)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := swap.execute(context.Background(), &executeConfig{
|
||||
statusChan: statusChan,
|
||||
blockEpochChan: blockEpochChan,
|
||||
timerFactory: timerFactory,
|
||||
sweeper: sweeper,
|
||||
batcher: batcher,
|
||||
cancelSwap: server.CancelLoopOutSwap,
|
||||
verifySchnorrSig: mockVerifySchnorrSigSuccess,
|
||||
}, ctx.Lnd.Height)
|
||||
|
|
@ -918,7 +985,7 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
}()
|
||||
|
||||
// The swap should be found in its initial state.
|
||||
cfg.store.(*storeMock).assertLoopOutStored()
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutStored()
|
||||
state := <-statusChan
|
||||
require.Equal(t, loopdb.StateInitiated, state.State)
|
||||
|
||||
|
|
@ -943,10 +1010,6 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
|
||||
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()
|
||||
|
|
@ -957,10 +1020,19 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
// preimage is not revealed, we also do not expect a preimage push.
|
||||
expiryChan <- testTime
|
||||
|
||||
// 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)
|
||||
|
||||
ctx.AssertEpochListeners(1)
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// When using taproot htlcs the flow is different as we do reveal the
|
||||
// preimage before sweeping in order for the server to trust us with
|
||||
// our MuSig2 signing attempts.
|
||||
cfg.store.(*storeMock).assertLoopOutState(
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(
|
||||
loopdb.StatePreimageRevealed,
|
||||
)
|
||||
status := <-statusChan
|
||||
|
|
@ -988,6 +1060,10 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
// Now when we report a new block and tick our expiry fee timer, and
|
||||
// fees are acceptably low so we expect our sweep to be published.
|
||||
blockEpochChan <- ctx.Lnd.Height + 2
|
||||
|
||||
err = ctx.Lnd.NotifyHeight(ctx.Lnd.Height + 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
expiryChan <- testTime
|
||||
|
||||
preimage = <-server.preimagePush
|
||||
|
|
@ -1010,7 +1086,11 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
|
|||
// spend our sweepTx and assert that the swap succeeds.
|
||||
ctx.NotifySpend(sweepTx, 0)
|
||||
|
||||
cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
|
||||
// After receiving the spend ntfn the batch will start monitoring for
|
||||
// confs.
|
||||
ctx.AssertRegisterConf(true, 3)
|
||||
|
||||
cfg.store.(*loopdb.StoreMock).AssertLoopOutState(loopdb.StateSuccess)
|
||||
status = <-statusChan
|
||||
require.Equal(t, status.State, loopdb.StateSuccess)
|
||||
require.NoError(t, <-errChan)
|
||||
|
|
|
|||
1070
looprpc/client.pb.go
1070
looprpc/client.pb.go
File diff suppressed because it is too large
Load diff
|
|
@ -235,6 +235,13 @@ message LoopOutRequest {
|
|||
The address type of the account specified in the account field.
|
||||
*/
|
||||
AddressType account_addr_type = 16;
|
||||
|
||||
/*
|
||||
A flag indicating whether the defined destination address does not belong to
|
||||
the wallet. This is used to flag whether this loop out swap could have its
|
||||
associated sweep batched.
|
||||
*/
|
||||
bool is_external_addr = 17;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1110,6 +1110,10 @@
|
|||
"account_addr_type": {
|
||||
"$ref": "#/definitions/looprpcAddressType",
|
||||
"description": "The address type of the account specified in the account field."
|
||||
},
|
||||
"is_external_addr": {
|
||||
"type": "boolean",
|
||||
"description": "A flag indicating whether the defined destination address does not belong to\nthe wallet. This is used to flag whether this loop out swap could have its\nassociated sweep batched."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ This file tracks release notes for the loop client.
|
|||
|
||||
#### New Features
|
||||
|
||||
* Sweep Batcher: A new sub-system was added that handles all the loopout
|
||||
sweeps. Successful loopout HTLCs will no longer be swept back to the wallet via
|
||||
individual transactions but will instead form a single transaction that holds
|
||||
multiple inputs and pays to a single output. This will significantly reduce
|
||||
chain fee costs as it's using less block space by directly consolidating all the
|
||||
htlcs to a single address. Loopouts that pay to non-wallet addresses will still
|
||||
use individual transactions as their output cannot be mutated.
|
||||
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
#### Bug Fixes
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
|
|
@ -268,6 +269,15 @@ func (s *serverMock) MuSig2SignSweep(_ context.Context, _ loopdb.ProtocolVersion
|
|||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (s *serverMock) MultiMuSig2SignSweep(ctx context.Context,
|
||||
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
|
||||
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
|
||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (s *serverMock) PushKey(_ context.Context, _ loopdb.ProtocolVersion,
|
||||
_ lntypes.Hash, _ [32]byte) error {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,322 +0,0 @@
|
|||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// storeMock implements a mock client swap store.
|
||||
type storeMock struct {
|
||||
loopOutSwaps map[lntypes.Hash]*loopdb.LoopOutContract
|
||||
loopOutUpdates map[lntypes.Hash][]loopdb.SwapStateData
|
||||
loopOutStoreChan chan loopdb.LoopOutContract
|
||||
loopOutUpdateChan chan loopdb.SwapStateData
|
||||
|
||||
loopInSwaps map[lntypes.Hash]*loopdb.LoopInContract
|
||||
loopInUpdates map[lntypes.Hash][]loopdb.SwapStateData
|
||||
loopInStoreChan chan loopdb.LoopInContract
|
||||
loopInUpdateChan chan loopdb.SwapStateData
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// NewStoreMock instantiates a new mock store.
|
||||
func newStoreMock(t *testing.T) *storeMock {
|
||||
return &storeMock{
|
||||
loopOutStoreChan: make(chan loopdb.LoopOutContract, 1),
|
||||
loopOutUpdateChan: make(chan loopdb.SwapStateData, 1),
|
||||
loopOutSwaps: make(map[lntypes.Hash]*loopdb.LoopOutContract),
|
||||
loopOutUpdates: make(map[lntypes.Hash][]loopdb.SwapStateData),
|
||||
|
||||
loopInStoreChan: make(chan loopdb.LoopInContract, 1),
|
||||
loopInUpdateChan: make(chan loopdb.SwapStateData, 1),
|
||||
loopInSwaps: make(map[lntypes.Hash]*loopdb.LoopInContract),
|
||||
loopInUpdates: make(map[lntypes.Hash][]loopdb.SwapStateData),
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// FetchLoopOutSwaps returns all swaps currently in the store.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) FetchLoopOutSwaps(ctx context.Context) ([]*loopdb.LoopOut, error) {
|
||||
result := []*loopdb.LoopOut{}
|
||||
|
||||
for hash, contract := range s.loopOutSwaps {
|
||||
updates := s.loopOutUpdates[hash]
|
||||
events := make([]*loopdb.LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &loopdb.LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &loopdb.LoopOut{
|
||||
Loop: loopdb.Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
result = append(result, swap)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FetchLoopOutSwaps returns all swaps currently in the store.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) FetchLoopOutSwap(ctx context.Context,
|
||||
hash lntypes.Hash) (*loopdb.LoopOut, error) {
|
||||
|
||||
contract, ok := s.loopOutSwaps[hash]
|
||||
if !ok {
|
||||
return nil, errors.New("swap not found")
|
||||
}
|
||||
|
||||
updates := s.loopOutUpdates[hash]
|
||||
events := make([]*loopdb.LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &loopdb.LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &loopdb.LoopOut{
|
||||
Loop: loopdb.Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
|
||||
return swap, nil
|
||||
}
|
||||
|
||||
// CreateLoopOut adds an initiated swap to the store.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) CreateLoopOut(ctx context.Context, hash lntypes.Hash,
|
||||
swap *loopdb.LoopOutContract) error {
|
||||
|
||||
_, ok := s.loopOutSwaps[hash]
|
||||
if ok {
|
||||
return errors.New("swap already exists")
|
||||
}
|
||||
|
||||
s.loopOutSwaps[hash] = swap
|
||||
s.loopOutUpdates[hash] = []loopdb.SwapStateData{}
|
||||
s.loopOutStoreChan <- *swap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchLoopInSwaps returns all in swaps currently in the store.
|
||||
func (s *storeMock) FetchLoopInSwaps(ctx context.Context) ([]*loopdb.LoopIn,
|
||||
error) {
|
||||
|
||||
result := []*loopdb.LoopIn{}
|
||||
|
||||
for hash, contract := range s.loopInSwaps {
|
||||
updates := s.loopInUpdates[hash]
|
||||
events := make([]*loopdb.LoopEvent, len(updates))
|
||||
for i, u := range updates {
|
||||
events[i] = &loopdb.LoopEvent{
|
||||
SwapStateData: u,
|
||||
}
|
||||
}
|
||||
|
||||
swap := &loopdb.LoopIn{
|
||||
Loop: loopdb.Loop{
|
||||
Hash: hash,
|
||||
Events: events,
|
||||
},
|
||||
Contract: contract,
|
||||
}
|
||||
result = append(result, swap)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateLoopIn adds an initiated loop in swap to the store.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) CreateLoopIn(ctx context.Context, hash lntypes.Hash,
|
||||
swap *loopdb.LoopInContract) error {
|
||||
|
||||
_, ok := s.loopInSwaps[hash]
|
||||
if ok {
|
||||
return errors.New("swap already exists")
|
||||
}
|
||||
|
||||
s.loopInSwaps[hash] = swap
|
||||
s.loopInUpdates[hash] = []loopdb.SwapStateData{}
|
||||
s.loopInStoreChan <- *swap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoopOut stores a new event for a target loop out swap. This appends to
|
||||
// the event log for a particular swap as it goes through the various stages in
|
||||
// its lifetime.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) UpdateLoopOut(ctx context.Context, hash lntypes.Hash,
|
||||
time time.Time, state loopdb.SwapStateData) error {
|
||||
|
||||
updates, ok := s.loopOutUpdates[hash]
|
||||
if !ok {
|
||||
return errors.New("swap does not exists")
|
||||
}
|
||||
|
||||
updates = append(updates, state)
|
||||
s.loopOutUpdates[hash] = updates
|
||||
s.loopOutUpdateChan <- state
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoopIn stores a new event for a target loop in swap. This appends to
|
||||
// the event log for a particular swap as it goes through the various stages in
|
||||
// its lifetime.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) UpdateLoopIn(ctx context.Context, hash lntypes.Hash,
|
||||
time time.Time, state loopdb.SwapStateData) error {
|
||||
|
||||
updates, ok := s.loopInUpdates[hash]
|
||||
if !ok {
|
||||
return errors.New("swap does not exists")
|
||||
}
|
||||
|
||||
updates = append(updates, state)
|
||||
s.loopInUpdates[hash] = updates
|
||||
s.loopInUpdateChan <- state
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutLiquidityParams writes the serialized `manager.Parameters` bytes into the
|
||||
// bucket.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) PutLiquidityParams(ctx context.Context,
|
||||
params []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchLiquidityParams reads the serialized `manager.Parameters` bytes from
|
||||
// the bucket.
|
||||
//
|
||||
// NOTE: Part of the loopdb.SwapStore interface.
|
||||
func (s *storeMock) FetchLiquidityParams(ctx context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *storeMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *storeMock) isDone() error {
|
||||
select {
|
||||
case <-s.loopOutStoreChan:
|
||||
return errors.New("storeChan not empty")
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-s.loopOutUpdateChan:
|
||||
return errors.New("updateChan not empty")
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *storeMock) assertLoopOutStored() {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case <-s.loopOutStoreChan:
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeMock) assertLoopOutState(expectedState loopdb.SwapState) {
|
||||
s.t.Helper()
|
||||
|
||||
state := <-s.loopOutUpdateChan
|
||||
if state.State != expectedState {
|
||||
s.t.Fatalf("expected state %v, got %v", expectedState, state)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeMock) assertLoopInStored() {
|
||||
s.t.Helper()
|
||||
|
||||
<-s.loopInStoreChan
|
||||
}
|
||||
|
||||
// assertLoopInState asserts that a specified state transition is persisted to
|
||||
// disk.
|
||||
func (s *storeMock) assertLoopInState(
|
||||
expectedState loopdb.SwapState) loopdb.SwapStateData {
|
||||
|
||||
s.t.Helper()
|
||||
|
||||
state := <-s.loopInUpdateChan
|
||||
require.Equal(s.t, expectedState, state.State)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *storeMock) assertStorePreimageReveal() {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case state := <-s.loopOutUpdateChan:
|
||||
require.Equal(s.t, loopdb.StatePreimageRevealed, state.State)
|
||||
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be marked as preimage revealed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storeMock) assertStoreFinished(expectedResult loopdb.SwapState) {
|
||||
s.t.Helper()
|
||||
|
||||
select {
|
||||
case state := <-s.loopOutUpdateChan:
|
||||
require.Equal(s.t, expectedResult, state.State)
|
||||
|
||||
case <-time.After(test.Timeout):
|
||||
s.t.Fatalf("expected swap to be finished")
|
||||
}
|
||||
}
|
||||
func (b *storeMock) BatchCreateLoopOut(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*loopdb.LoopOutContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *storeMock) BatchCreateLoopIn(ctx context.Context,
|
||||
swaps map[lntypes.Hash]*loopdb.LoopInContract) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *storeMock) BatchInsertUpdate(ctx context.Context,
|
||||
updateData map[lntypes.Hash][]loopdb.BatchInsertUpdateData) error {
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
54
swap.go
54
swap.go
|
|
@ -4,11 +4,10 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
|
|
@ -50,59 +49,10 @@ func newSwapKit(hash lntypes.Hash, swapType swap.Type, cfg *swapConfig,
|
|||
}
|
||||
}
|
||||
|
||||
// GetHtlcScriptVersion returns the correct HTLC script version for the passed
|
||||
// protocol version.
|
||||
func GetHtlcScriptVersion(
|
||||
protocolVersion loopdb.ProtocolVersion) swap.ScriptVersion {
|
||||
|
||||
// If the swap was initiated before we had our v3 script, use v2.
|
||||
if protocolVersion < loopdb.ProtocolVersionHtlcV3 ||
|
||||
protocolVersion == loopdb.ProtocolVersionUnrecorded {
|
||||
|
||||
return swap.HtlcV2
|
||||
}
|
||||
|
||||
return swap.HtlcV3
|
||||
}
|
||||
|
||||
// IsTaproot returns true if the swap referenced by the passed swap contract
|
||||
// uses the v3 (taproot) htlc.
|
||||
func IsTaprootSwap(swapContract *loopdb.SwapContract) bool {
|
||||
return GetHtlcScriptVersion(swapContract.ProtocolVersion) == swap.HtlcV3
|
||||
}
|
||||
|
||||
// GetHtlc composes and returns the on-chain swap script.
|
||||
func GetHtlc(hash lntypes.Hash, contract *loopdb.SwapContract,
|
||||
chainParams *chaincfg.Params) (*swap.Htlc, error) {
|
||||
|
||||
switch GetHtlcScriptVersion(contract.ProtocolVersion) {
|
||||
case swap.HtlcV2:
|
||||
return swap.NewHtlcV2(
|
||||
contract.CltvExpiry, contract.HtlcKeys.SenderScriptKey,
|
||||
contract.HtlcKeys.ReceiverScriptKey, hash,
|
||||
chainParams,
|
||||
)
|
||||
|
||||
case swap.HtlcV3:
|
||||
// Swaps that implement the new MuSig2 protocol will be expected
|
||||
// to use the 1.0RC2 MuSig2 key derivation scheme.
|
||||
muSig2Version := input.MuSig2Version040
|
||||
if contract.ProtocolVersion >= loopdb.ProtocolVersionMuSig2 {
|
||||
muSig2Version = input.MuSig2Version100RC2
|
||||
}
|
||||
|
||||
return swap.NewHtlcV3(
|
||||
muSig2Version,
|
||||
contract.CltvExpiry,
|
||||
contract.HtlcKeys.SenderInternalPubKey,
|
||||
contract.HtlcKeys.ReceiverInternalPubKey,
|
||||
contract.HtlcKeys.SenderScriptKey,
|
||||
contract.HtlcKeys.ReceiverScriptKey,
|
||||
hash, chainParams,
|
||||
)
|
||||
}
|
||||
|
||||
return nil, swap.ErrInvalidScriptVersion
|
||||
return utils.GetHtlcScriptVersion(swapContract.ProtocolVersion) == swap.HtlcV3
|
||||
}
|
||||
|
||||
// swapInfo constructs and returns a filled SwapInfo from
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/aperture/lsat"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
looprpc "github.com/lightninglabs/loop/swapserverrpc"
|
||||
|
|
@ -775,6 +776,51 @@ func (s *grpcSwapServerClient) MuSig2SignSweep(ctx context.Context,
|
|||
return res.Nonce, res.PartialSignature, nil
|
||||
}
|
||||
|
||||
// MultiMuSig2SignSweep calls the server to cooperatively sign an input in
|
||||
// a batch transaction that attempts to sweep multiple htlcs at once. This
|
||||
// method is called once per input signed. The prevoutMap is a map of all the
|
||||
// prevout information for each spend outpoint. Returns the server's nonce and
|
||||
// partial signature.
|
||||
func (s *grpcSwapServerClient) MultiMuSig2SignSweep(ctx context.Context,
|
||||
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
|
||||
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
|
||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
prevOutInfo := make([]*looprpc.PrevoutInfo, 0, len(prevoutMap))
|
||||
for prevOut, txOut := range prevoutMap {
|
||||
txOut := *txOut
|
||||
prevOut := prevOut
|
||||
|
||||
prevOutInfo = append(prevOutInfo,
|
||||
&looprpc.PrevoutInfo{
|
||||
TxidBytes: prevOut.Hash[:],
|
||||
OutputIndex: prevOut.Index,
|
||||
Value: uint64(txOut.Value),
|
||||
PkScript: txOut.PkScript,
|
||||
})
|
||||
}
|
||||
|
||||
req := &looprpc.MuSig2SignSweepReq{
|
||||
ProtocolVersion: looprpc.ProtocolVersion(protocolVersion),
|
||||
SwapHash: swapHash[:],
|
||||
PaymentAddress: paymentAddr[:],
|
||||
Nonce: nonce,
|
||||
SweepTxPsbt: sweepTxPsbt,
|
||||
PrevoutInfo: prevOutInfo,
|
||||
}
|
||||
|
||||
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
||||
defer rpcCancel()
|
||||
|
||||
res, err := s.server.MuSig2SignSweep(rpcCtx, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return res.Nonce, res.PartialSignature, nil
|
||||
}
|
||||
|
||||
// PushKey sends the client's HTLC internal key associated with the swap to
|
||||
// the server.
|
||||
func (s *grpcSwapServerClient) PushKey(ctx context.Context,
|
||||
|
|
|
|||
|
|
@ -1973,8 +1973,7 @@ type ServerProbeRequest 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"`
|
||||
// The probe amount.
|
||||
Amt uint64 `protobuf:"varint,2,opt,name=amt,proto3" json:"amt,omitempty"`
|
||||
Amt uint64 `protobuf:"varint,2,opt,name=amt,proto3" json:"amt,omitempty"`
|
||||
// The target node for the probe.
|
||||
Target []byte `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"`
|
||||
// Optional last hop to use when probing the client.
|
||||
|
|
@ -2357,6 +2356,8 @@ type MuSig2SignSweepReq struct {
|
|||
Nonce []byte `protobuf:"bytes,4,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// The psbt of the sweep txn.
|
||||
SweepTxPsbt []byte `protobuf:"bytes,6,opt,name=sweep_tx_psbt,json=sweepTxPsbt,proto3" json:"sweep_tx_psbt,omitempty"`
|
||||
// The prevout information of the sweep txn.
|
||||
PrevoutInfo []*PrevoutInfo `protobuf:"bytes,7,rep,name=prevout_info,json=prevoutInfo,proto3" json:"prevout_info,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MuSig2SignSweepReq) Reset() {
|
||||
|
|
@ -2426,6 +2427,88 @@ func (x *MuSig2SignSweepReq) GetSweepTxPsbt() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *MuSig2SignSweepReq) GetPrevoutInfo() []*PrevoutInfo {
|
||||
if x != nil {
|
||||
return x.PrevoutInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PrevoutInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The value of the txout.
|
||||
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
// The pk_script of the txout.
|
||||
PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
|
||||
// The txid of the txout.
|
||||
TxidBytes []byte `protobuf:"bytes,3,opt,name=txid_bytes,json=txidBytes,proto3" json:"txid_bytes,omitempty"`
|
||||
// The index of the txout.
|
||||
OutputIndex uint32 `protobuf:"varint,4,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) Reset() {
|
||||
*x = PrevoutInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[28]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PrevoutInfo) ProtoMessage() {}
|
||||
|
||||
func (x *PrevoutInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[28]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PrevoutInfo.ProtoReflect.Descriptor instead.
|
||||
func (*PrevoutInfo) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{28}
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) GetValue() uint64 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) GetPkScript() []byte {
|
||||
if x != nil {
|
||||
return x.PkScript
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) GetTxidBytes() []byte {
|
||||
if x != nil {
|
||||
return x.TxidBytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *PrevoutInfo) GetOutputIndex() uint32 {
|
||||
if x != nil {
|
||||
return x.OutputIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type MuSig2SignSweepRes struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
|
@ -2440,7 +2523,7 @@ type MuSig2SignSweepRes struct {
|
|||
func (x *MuSig2SignSweepRes) Reset() {
|
||||
*x = MuSig2SignSweepRes{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[28]
|
||||
mi := &file_server_proto_msgTypes[29]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2453,7 +2536,7 @@ func (x *MuSig2SignSweepRes) String() string {
|
|||
func (*MuSig2SignSweepRes) ProtoMessage() {}
|
||||
|
||||
func (x *MuSig2SignSweepRes) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[28]
|
||||
mi := &file_server_proto_msgTypes[29]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2466,7 +2549,7 @@ func (x *MuSig2SignSweepRes) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use MuSig2SignSweepRes.ProtoReflect.Descriptor instead.
|
||||
func (*MuSig2SignSweepRes) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{28}
|
||||
return file_server_proto_rawDescGZIP(), []int{29}
|
||||
}
|
||||
|
||||
func (x *MuSig2SignSweepRes) GetNonce() []byte {
|
||||
|
|
@ -2499,7 +2582,7 @@ type ServerPushKeyReq struct {
|
|||
func (x *ServerPushKeyReq) Reset() {
|
||||
*x = ServerPushKeyReq{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[29]
|
||||
mi := &file_server_proto_msgTypes[30]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2512,7 +2595,7 @@ func (x *ServerPushKeyReq) String() string {
|
|||
func (*ServerPushKeyReq) ProtoMessage() {}
|
||||
|
||||
func (x *ServerPushKeyReq) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[29]
|
||||
mi := &file_server_proto_msgTypes[30]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2525,7 +2608,7 @@ func (x *ServerPushKeyReq) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ServerPushKeyReq.ProtoReflect.Descriptor instead.
|
||||
func (*ServerPushKeyReq) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{29}
|
||||
return file_server_proto_rawDescGZIP(), []int{30}
|
||||
}
|
||||
|
||||
func (x *ServerPushKeyReq) GetProtocolVersion() ProtocolVersion {
|
||||
|
|
@ -2558,7 +2641,7 @@ type ServerPushKeyRes struct {
|
|||
func (x *ServerPushKeyRes) Reset() {
|
||||
*x = ServerPushKeyRes{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[30]
|
||||
mi := &file_server_proto_msgTypes[31]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2571,7 +2654,7 @@ func (x *ServerPushKeyRes) String() string {
|
|||
func (*ServerPushKeyRes) ProtoMessage() {}
|
||||
|
||||
func (x *ServerPushKeyRes) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[30]
|
||||
mi := &file_server_proto_msgTypes[31]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2584,7 +2667,7 @@ func (x *ServerPushKeyRes) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ServerPushKeyRes.ProtoReflect.Descriptor instead.
|
||||
func (*ServerPushKeyRes) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{30}
|
||||
return file_server_proto_rawDescGZIP(), []int{31}
|
||||
}
|
||||
|
||||
// FetchL402Request is an empty request sent from the client to the server to
|
||||
|
|
@ -2598,7 +2681,7 @@ type FetchL402Request struct {
|
|||
func (x *FetchL402Request) Reset() {
|
||||
*x = FetchL402Request{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[31]
|
||||
mi := &file_server_proto_msgTypes[32]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2611,7 +2694,7 @@ func (x *FetchL402Request) String() string {
|
|||
func (*FetchL402Request) ProtoMessage() {}
|
||||
|
||||
func (x *FetchL402Request) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[31]
|
||||
mi := &file_server_proto_msgTypes[32]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2624,7 +2707,7 @@ func (x *FetchL402Request) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use FetchL402Request.ProtoReflect.Descriptor instead.
|
||||
func (*FetchL402Request) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{31}
|
||||
return file_server_proto_rawDescGZIP(), []int{32}
|
||||
}
|
||||
|
||||
// FetchL402Response is an empty response sent from the server to the client to
|
||||
|
|
@ -2638,7 +2721,7 @@ type FetchL402Response struct {
|
|||
func (x *FetchL402Response) Reset() {
|
||||
*x = FetchL402Response{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_server_proto_msgTypes[32]
|
||||
mi := &file_server_proto_msgTypes[33]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -2651,7 +2734,7 @@ func (x *FetchL402Response) String() string {
|
|||
func (*FetchL402Response) ProtoMessage() {}
|
||||
|
||||
func (x *FetchL402Response) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_server_proto_msgTypes[32]
|
||||
mi := &file_server_proto_msgTypes[33]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -2664,7 +2747,7 @@ func (x *FetchL402Response) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use FetchL402Response.ProtoReflect.Descriptor instead.
|
||||
func (*FetchL402Response) Descriptor() ([]byte, []int) {
|
||||
return file_server_proto_rawDescGZIP(), []int{32}
|
||||
return file_server_proto_rawDescGZIP(), []int{33}
|
||||
}
|
||||
|
||||
var File_server_proto protoreflect.FileDescriptor
|
||||
|
|
@ -2947,7 +3030,7 @@ var file_server_proto_rawDesc = []byte{
|
|||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61,
|
||||
0x6c, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x22,
|
||||
0xdf, 0x01, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77,
|
||||
0x98, 0x02, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77,
|
||||
0x65, 0x65, 0x70, 0x52, 0x65, 0x71, 0x12, 0x43, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||
0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f,
|
||||
|
|
@ -2960,188 +3043,200 @@ var file_server_proto_rawDesc = []byte{
|
|||
0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x77, 0x65, 0x65, 0x70,
|
||||
0x5f, 0x74, 0x78, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b,
|
||||
0x73, 0x77, 0x65, 0x65, 0x70, 0x54, 0x78, 0x50, 0x73, 0x62, 0x74, 0x4a, 0x04, 0x08, 0x05, 0x10,
|
||||
0x06, 0x22, 0x57, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53,
|
||||
0x77, 0x65, 0x65, 0x70, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a,
|
||||
0x11, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
|
||||
0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61,
|
||||
0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x10, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12,
|
||||
0x43, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73,
|
||||
0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73,
|
||||
0x68, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72,
|
||||
0x69, 0x76, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x76, 0x6b, 0x65, 0x79, 0x22, 0x12, 0x0a, 0x10,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
||||
0x22, 0x12, 0x0a, 0x10, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x22, 0x13, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30,
|
||||
0x32, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xef, 0x01, 0x0a, 0x0f, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x55, 0x4c,
|
||||
0x54, 0x49, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a,
|
||||
0x15, 0x4e, 0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x45, 0x47, 0x57, 0x49, 0x54, 0x5f, 0x4c,
|
||||
0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x45, 0x49,
|
||||
0x4d, 0x41, 0x47, 0x45, 0x5f, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f,
|
||||
0x55, 0x54, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x50,
|
||||
0x49, 0x52, 0x59, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0b,
|
||||
0x0a, 0x07, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x56, 0x32, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x4d,
|
||||
0x55, 0x4c, 0x54, 0x49, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x13,
|
||||
0x0a, 0x0f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45,
|
||||
0x4c, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x4f, 0x42, 0x45, 0x10, 0x08, 0x12, 0x12,
|
||||
0x0a, 0x0e, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e,
|
||||
0x10, 0x09, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x56, 0x33, 0x10, 0x0a, 0x12,
|
||||
0x0a, 0x0a, 0x06, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x10, 0x0b, 0x2a, 0x9e, 0x04, 0x0a, 0x0f,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||
0x14, 0x0a, 0x10, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41,
|
||||
0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f,
|
||||
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x01,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45,
|
||||
0x53, 0x53, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46,
|
||||
0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x12,
|
||||
0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
|
||||
0x5f, 0x4e, 0x4f, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x45,
|
||||
0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41,
|
||||
0x4c, 0x49, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10,
|
||||
0x05, 0x12, 0x23, 0x0a, 0x1f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
|
||||
0x45, 0x44, 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d,
|
||||
0x45, 0x4f, 0x55, 0x54, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52,
|
||||
0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10,
|
||||
0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
|
||||
0x45, 0x44, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45,
|
||||
0x10, 0x08, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x45, 0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x41,
|
||||
0x54, 0x49, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52,
|
||||
0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48,
|
||||
0x45, 0x44, 0x10, 0x0a, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55,
|
||||
0x4e, 0x45, 0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52,
|
||||
0x45, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48, 0x54,
|
||||
0x4c, 0x43, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0c, 0x12, 0x1f,
|
||||
0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f,
|
||||
0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x0d, 0x12,
|
||||
0x20, 0x0a, 0x1c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54,
|
||||
0x5f, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10,
|
||||
0x0e, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c,
|
||||
0x45, 0x44, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x50, 0x4c, 0x45, 0x5f, 0x53, 0x57, 0x41, 0x50,
|
||||
0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x53, 0x10, 0x0f, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x45,
|
||||
0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x49, 0x54,
|
||||
0x49, 0x41, 0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x10, 0x2a, 0x4a, 0x0a, 0x10,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x11, 0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,
|
||||
0x4e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x52, 0x4f,
|
||||
0x55, 0x54, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45,
|
||||
0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x2a, 0xf1, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f,
|
||||
0x6e, 0x12, 0x1b, 0x0a, 0x17, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45,
|
||||
0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1e,
|
||||
0x0a, 0x1a, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45,
|
||||
0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1f,
|
||||
0x0a, 0x1b, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45,
|
||||
0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12,
|
||||
0x1c, 0x0a, 0x18, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52,
|
||||
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x30, 0x0a,
|
||||
0x2c, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
|
||||
0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41,
|
||||
0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12,
|
||||
0x2b, 0x0a, 0x27, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52,
|
||||
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45,
|
||||
0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0x27, 0x0a, 0x0d,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x57, 0x5f, 0x48,
|
||||
0x49, 0x47, 0x48, 0x10, 0x01, 0x32, 0xdc, 0x0a, 0x0a, 0x0a, 0x53, 0x77, 0x61, 0x70, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54,
|
||||
0x65, 0x72, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74,
|
||||
0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70,
|
||||
0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75,
|
||||
0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x29, 0x2e,
|
||||
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f,
|
||||
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74,
|
||||
0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51,
|
||||
0x75, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74,
|
||||
0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54,
|
||||
0x65, 0x72, 0x6d, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65,
|
||||
0x72, 0x6d, 0x73, 0x12, 0x4c, 0x0a, 0x0d, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e,
|
||||
0x53, 0x77, 0x61, 0x70, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x54, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65,
|
||||
0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01,
|
||||
0x12, 0x65, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f,
|
||||
0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f,
|
||||
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6c,
|
||||
0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
|
||||
0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5a, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65,
|
||||
0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x21, 0x2e, 0x6c,
|
||||
0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f,
|
||||
0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c,
|
||||
0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x1b, 0x2e, 0x6c,
|
||||
0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f,
|
||||
0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70,
|
||||
0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69,
|
||||
0x6e, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67,
|
||||
0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x13, 0x52, 0x65, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x12, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65,
|
||||
0x71, 0x1a, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52,
|
||||
0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0f, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e,
|
||||
0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52,
|
||||
0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53,
|
||||
0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x65, 0x73, 0x12,
|
||||
0x3f, 0x0a, 0x07, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x6c, 0x6f, 0x6f,
|
||||
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b,
|
||||
0x65, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
||||
0x12, 0x42, 0x0a, 0x09, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x12, 0x19, 0x2e,
|
||||
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30,
|
||||
0x32, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 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, 0x77, 0x65, 0x65, 0x70, 0x54, 0x78, 0x50, 0x73, 0x62, 0x74, 0x12, 0x37, 0x0a, 0x0c, 0x70,
|
||||
0x72, 0x65, 0x76, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x07, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x14, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x65, 0x76,
|
||||
0x6f, 0x75, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x6f, 0x75, 0x74,
|
||||
0x49, 0x6e, 0x66, 0x6f, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x82, 0x01, 0x0a, 0x0b, 0x50,
|
||||
0x72, 0x65, 0x76, 0x6f, 0x75, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1d, 0x0a,
|
||||
0x0a, 0x74, 0x78, 0x69, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x09, 0x74, 0x78, 0x69, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22,
|
||||
0x57, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77, 0x65,
|
||||
0x65, 0x70, 0x52, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x70,
|
||||
0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x12, 0x43, 0x0a,
|
||||
0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12,
|
||||
0x29, 0x0a, 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72, 0x69, 0x76,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x76, 0x6b, 0x65, 0x79, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x22, 0x12,
|
||||
0x0a, 0x10, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0x13, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0xef, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x4c,
|
||||
0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x55, 0x4c, 0x54, 0x49,
|
||||
0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x4e,
|
||||
0x41, 0x54, 0x49, 0x56, 0x45, 0x5f, 0x53, 0x45, 0x47, 0x57, 0x49, 0x54, 0x5f, 0x4c, 0x4f, 0x4f,
|
||||
0x50, 0x5f, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x50, 0x52, 0x45, 0x49, 0x4d, 0x41,
|
||||
0x47, 0x45, 0x5f, 0x50, 0x55, 0x53, 0x48, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54,
|
||||
0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52,
|
||||
0x59, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07,
|
||||
0x48, 0x54, 0x4c, 0x43, 0x5f, 0x56, 0x32, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x55, 0x4c,
|
||||
0x54, 0x49, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f,
|
||||
0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10,
|
||||
0x07, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x4f, 0x42, 0x45, 0x10, 0x08, 0x12, 0x12, 0x0a, 0x0e,
|
||||
0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e, 0x10, 0x09,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x56, 0x33, 0x10, 0x0a, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x10, 0x0b, 0x2a, 0x9e, 0x04, 0x0a, 0x0f, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x10, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x45,
|
||||
0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48, 0x54,
|
||||
0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12,
|
||||
0x0a, 0x0e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53,
|
||||
0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x12, 0x19, 0x0a,
|
||||
0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x4e,
|
||||
0x4f, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x45, 0x52, 0x56,
|
||||
0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
|
||||
0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x05, 0x12,
|
||||
0x23, 0x0a, 0x1f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
|
||||
0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f,
|
||||
0x55, 0x54, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46,
|
||||
0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x07, 0x12,
|
||||
0x1f, 0x0a, 0x1b, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
|
||||
0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x44, 0x45, 0x41, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x08,
|
||||
0x12, 0x22, 0x0a, 0x1e, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45,
|
||||
0x44, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49,
|
||||
0x4f, 0x4e, 0x10, 0x09, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x54,
|
||||
0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44,
|
||||
0x10, 0x0a, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x45,
|
||||
0x58, 0x50, 0x45, 0x43, 0x54, 0x45, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10,
|
||||
0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48, 0x54, 0x4c, 0x43,
|
||||
0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x10, 0x0c, 0x12, 0x1f, 0x0a, 0x1b,
|
||||
0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x52,
|
||||
0x45, 0x50, 0x41, 0x59, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x0d, 0x12, 0x20, 0x0a,
|
||||
0x1c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x49,
|
||||
0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x0e, 0x12,
|
||||
0x27, 0x0a, 0x23, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44,
|
||||
0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x50, 0x4c, 0x45, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x53,
|
||||
0x43, 0x52, 0x49, 0x50, 0x54, 0x53, 0x10, 0x0f, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x45, 0x52, 0x56,
|
||||
0x45, 0x52, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41,
|
||||
0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x10, 0x2a, 0x4a, 0x0a, 0x10, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11,
|
||||
0x0a, 0x0d, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
|
||||
0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x5f, 0x52, 0x4f, 0x55, 0x54,
|
||||
0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x52,
|
||||
0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x2a, 0xf1, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
|
||||
0x1b, 0x0a, 0x17, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52,
|
||||
0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a,
|
||||
0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53,
|
||||
0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b,
|
||||
0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53,
|
||||
0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x1c, 0x0a,
|
||||
0x18, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
|
||||
0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x30, 0x0a, 0x2c, 0x4c,
|
||||
0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f,
|
||||
0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d,
|
||||
0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x2b, 0x0a,
|
||||
0x27, 0x4c, 0x4e, 0x44, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41,
|
||||
0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54,
|
||||
0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0x27, 0x0a, 0x0d, 0x52, 0x6f,
|
||||
0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x4e,
|
||||
0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x57, 0x5f, 0x48, 0x49, 0x47,
|
||||
0x48, 0x10, 0x01, 0x32, 0xdc, 0x0a, 0x0a, 0x0a, 0x53, 0x77, 0x61, 0x70, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72,
|
||||
0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65,
|
||||
0x72, 0x6d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75,
|
||||
0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50,
|
||||
0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x29, 0x2e, 0x6c, 0x6f,
|
||||
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70,
|
||||
0x4f, 0x75, 0x74, 0x50, 0x75, 0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x75,
|
||||
0x73, 0x68, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f,
|
||||
0x74, 0x65, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75,
|
||||
0x6f, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72,
|
||||
0x6d, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d,
|
||||
0x73, 0x12, 0x4c, 0x0a, 0x0d, 0x4e, 0x65, 0x77, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x53, 0x77,
|
||||
0x61, 0x70, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x54, 0x0a, 0x0b, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x21,
|
||||
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c,
|
||||
0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76,
|
||||
0x65, 0x72, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
|
||||
0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73,
|
||||
0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63,
|
||||
0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62,
|
||||
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x65,
|
||||
0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f, 0x6f, 0x70, 0x49,
|
||||
0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x6c, 0x6f, 0x6f,
|
||||
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4c, 0x6f,
|
||||
0x6f, 0x70, 0x49, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5a, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c,
|
||||
0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f,
|
||||
0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f,
|
||||
0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e,
|
||||
0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4c, 0x6f,
|
||||
0x6f, 0x70, 0x4f, 0x75, 0x74, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f,
|
||||
0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x16, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
|
||||
0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12,
|
||||
0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
|
||||
0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6c,
|
||||
0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72,
|
||||
0x74, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f,
|
||||
0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52,
|
||||
0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x1a,
|
||||
0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
|
||||
0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73,
|
||||
0x12, 0x4b, 0x0a, 0x0f, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77,
|
||||
0x65, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
|
||||
0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x65, 0x71,
|
||||
0x1a, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67,
|
||||
0x32, 0x53, 0x69, 0x67, 0x6e, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x65, 0x73, 0x12, 0x3f, 0x0a,
|
||||
0x07, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79,
|
||||
0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x50, 0x75, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x12, 0x42,
|
||||
0x0a, 0x09, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x12, 0x19, 0x2e, 0x6c, 0x6f,
|
||||
0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x4c, 0x34, 0x30, 0x32, 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 (
|
||||
|
|
@ -3157,7 +3252,7 @@ func file_server_proto_rawDescGZIP() []byte {
|
|||
}
|
||||
|
||||
var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||
var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 33)
|
||||
var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 34)
|
||||
var file_server_proto_goTypes = []interface{}{
|
||||
(ProtocolVersion)(0), // 0: looprpc.ProtocolVersion
|
||||
(ServerSwapState)(0), // 1: looprpc.ServerSwapState
|
||||
|
|
@ -3192,19 +3287,20 @@ var file_server_proto_goTypes = []interface{}{
|
|||
(*ReportRoutingResultReq)(nil), // 30: looprpc.ReportRoutingResultReq
|
||||
(*ReportRoutingResultRes)(nil), // 31: looprpc.ReportRoutingResultRes
|
||||
(*MuSig2SignSweepReq)(nil), // 32: looprpc.MuSig2SignSweepReq
|
||||
(*MuSig2SignSweepRes)(nil), // 33: looprpc.MuSig2SignSweepRes
|
||||
(*ServerPushKeyReq)(nil), // 34: looprpc.ServerPushKeyReq
|
||||
(*ServerPushKeyRes)(nil), // 35: looprpc.ServerPushKeyRes
|
||||
(*FetchL402Request)(nil), // 36: looprpc.FetchL402Request
|
||||
(*FetchL402Response)(nil), // 37: looprpc.FetchL402Response
|
||||
(*RouteHint)(nil), // 38: looprpc.RouteHint
|
||||
(*PrevoutInfo)(nil), // 33: looprpc.PrevoutInfo
|
||||
(*MuSig2SignSweepRes)(nil), // 34: looprpc.MuSig2SignSweepRes
|
||||
(*ServerPushKeyReq)(nil), // 35: looprpc.ServerPushKeyReq
|
||||
(*ServerPushKeyRes)(nil), // 36: looprpc.ServerPushKeyRes
|
||||
(*FetchL402Request)(nil), // 37: looprpc.FetchL402Request
|
||||
(*FetchL402Response)(nil), // 38: looprpc.FetchL402Response
|
||||
(*RouteHint)(nil), // 39: looprpc.RouteHint
|
||||
}
|
||||
var file_server_proto_depIdxs = []int32{
|
||||
0, // 0: looprpc.ServerLoopOutRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 1: looprpc.ServerLoopOutQuoteRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 2: looprpc.ServerLoopOutTermsRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 3: looprpc.ServerLoopInRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
38, // 4: looprpc.ServerLoopInQuoteRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
39, // 4: looprpc.ServerLoopInQuoteRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
0, // 5: looprpc.ServerLoopInQuoteRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 6: looprpc.ServerLoopInTermsRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 7: looprpc.ServerLoopOutPushPreimageRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
|
|
@ -3217,50 +3313,51 @@ var file_server_proto_depIdxs = []int32{
|
|||
0, // 14: looprpc.CancelLoopOutSwapRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
22, // 15: looprpc.CancelLoopOutSwapRequest.route_cancel:type_name -> looprpc.RouteCancel
|
||||
0, // 16: looprpc.ServerProbeRequest.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
38, // 17: looprpc.ServerProbeRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
39, // 17: looprpc.ServerProbeRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
0, // 18: looprpc.RecommendRoutingPluginReq.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
4, // 19: looprpc.RecommendRoutingPluginRes.plugin:type_name -> looprpc.RoutingPlugin
|
||||
0, // 20: looprpc.ReportRoutingResultReq.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
4, // 21: looprpc.ReportRoutingResultReq.plugin:type_name -> looprpc.RoutingPlugin
|
||||
0, // 22: looprpc.MuSig2SignSweepReq.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
0, // 23: looprpc.ServerPushKeyReq.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
9, // 24: looprpc.SwapServer.LoopOutTerms:input_type -> looprpc.ServerLoopOutTermsRequest
|
||||
5, // 25: looprpc.SwapServer.NewLoopOutSwap:input_type -> looprpc.ServerLoopOutRequest
|
||||
17, // 26: looprpc.SwapServer.LoopOutPushPreimage:input_type -> looprpc.ServerLoopOutPushPreimageRequest
|
||||
7, // 27: looprpc.SwapServer.LoopOutQuote:input_type -> looprpc.ServerLoopOutQuoteRequest
|
||||
15, // 28: looprpc.SwapServer.LoopInTerms:input_type -> looprpc.ServerLoopInTermsRequest
|
||||
11, // 29: looprpc.SwapServer.NewLoopInSwap:input_type -> looprpc.ServerLoopInRequest
|
||||
13, // 30: looprpc.SwapServer.LoopInQuote:input_type -> looprpc.ServerLoopInQuoteRequest
|
||||
19, // 31: looprpc.SwapServer.SubscribeLoopOutUpdates:input_type -> looprpc.SubscribeUpdatesRequest
|
||||
19, // 32: looprpc.SwapServer.SubscribeLoopInUpdates:input_type -> looprpc.SubscribeUpdatesRequest
|
||||
24, // 33: looprpc.SwapServer.CancelLoopOutSwap:input_type -> looprpc.CancelLoopOutSwapRequest
|
||||
26, // 34: looprpc.SwapServer.Probe:input_type -> looprpc.ServerProbeRequest
|
||||
28, // 35: looprpc.SwapServer.RecommendRoutingPlugin:input_type -> looprpc.RecommendRoutingPluginReq
|
||||
30, // 36: looprpc.SwapServer.ReportRoutingResult:input_type -> looprpc.ReportRoutingResultReq
|
||||
32, // 37: looprpc.SwapServer.MuSig2SignSweep:input_type -> looprpc.MuSig2SignSweepReq
|
||||
34, // 38: looprpc.SwapServer.PushKey:input_type -> looprpc.ServerPushKeyReq
|
||||
36, // 39: looprpc.SwapServer.FetchL402:input_type -> looprpc.FetchL402Request
|
||||
10, // 40: looprpc.SwapServer.LoopOutTerms:output_type -> looprpc.ServerLoopOutTerms
|
||||
6, // 41: looprpc.SwapServer.NewLoopOutSwap:output_type -> looprpc.ServerLoopOutResponse
|
||||
18, // 42: looprpc.SwapServer.LoopOutPushPreimage:output_type -> looprpc.ServerLoopOutPushPreimageResponse
|
||||
8, // 43: looprpc.SwapServer.LoopOutQuote:output_type -> looprpc.ServerLoopOutQuote
|
||||
16, // 44: looprpc.SwapServer.LoopInTerms:output_type -> looprpc.ServerLoopInTerms
|
||||
12, // 45: looprpc.SwapServer.NewLoopInSwap:output_type -> looprpc.ServerLoopInResponse
|
||||
14, // 46: looprpc.SwapServer.LoopInQuote:output_type -> looprpc.ServerLoopInQuoteResponse
|
||||
20, // 47: looprpc.SwapServer.SubscribeLoopOutUpdates:output_type -> looprpc.SubscribeLoopOutUpdatesResponse
|
||||
21, // 48: looprpc.SwapServer.SubscribeLoopInUpdates:output_type -> looprpc.SubscribeLoopInUpdatesResponse
|
||||
25, // 49: looprpc.SwapServer.CancelLoopOutSwap:output_type -> looprpc.CancelLoopOutSwapResponse
|
||||
27, // 50: looprpc.SwapServer.Probe:output_type -> looprpc.ServerProbeResponse
|
||||
29, // 51: looprpc.SwapServer.RecommendRoutingPlugin:output_type -> looprpc.RecommendRoutingPluginRes
|
||||
31, // 52: looprpc.SwapServer.ReportRoutingResult:output_type -> looprpc.ReportRoutingResultRes
|
||||
33, // 53: looprpc.SwapServer.MuSig2SignSweep:output_type -> looprpc.MuSig2SignSweepRes
|
||||
35, // 54: looprpc.SwapServer.PushKey:output_type -> looprpc.ServerPushKeyRes
|
||||
37, // 55: looprpc.SwapServer.FetchL402:output_type -> looprpc.FetchL402Response
|
||||
40, // [40:56] is the sub-list for method output_type
|
||||
24, // [24:40] is the sub-list for method input_type
|
||||
24, // [24:24] is the sub-list for extension type_name
|
||||
24, // [24:24] is the sub-list for extension extendee
|
||||
0, // [0:24] is the sub-list for field type_name
|
||||
33, // 23: looprpc.MuSig2SignSweepReq.prevout_info:type_name -> looprpc.PrevoutInfo
|
||||
0, // 24: looprpc.ServerPushKeyReq.protocol_version:type_name -> looprpc.ProtocolVersion
|
||||
9, // 25: looprpc.SwapServer.LoopOutTerms:input_type -> looprpc.ServerLoopOutTermsRequest
|
||||
5, // 26: looprpc.SwapServer.NewLoopOutSwap:input_type -> looprpc.ServerLoopOutRequest
|
||||
17, // 27: looprpc.SwapServer.LoopOutPushPreimage:input_type -> looprpc.ServerLoopOutPushPreimageRequest
|
||||
7, // 28: looprpc.SwapServer.LoopOutQuote:input_type -> looprpc.ServerLoopOutQuoteRequest
|
||||
15, // 29: looprpc.SwapServer.LoopInTerms:input_type -> looprpc.ServerLoopInTermsRequest
|
||||
11, // 30: looprpc.SwapServer.NewLoopInSwap:input_type -> looprpc.ServerLoopInRequest
|
||||
13, // 31: looprpc.SwapServer.LoopInQuote:input_type -> looprpc.ServerLoopInQuoteRequest
|
||||
19, // 32: looprpc.SwapServer.SubscribeLoopOutUpdates:input_type -> looprpc.SubscribeUpdatesRequest
|
||||
19, // 33: looprpc.SwapServer.SubscribeLoopInUpdates:input_type -> looprpc.SubscribeUpdatesRequest
|
||||
24, // 34: looprpc.SwapServer.CancelLoopOutSwap:input_type -> looprpc.CancelLoopOutSwapRequest
|
||||
26, // 35: looprpc.SwapServer.Probe:input_type -> looprpc.ServerProbeRequest
|
||||
28, // 36: looprpc.SwapServer.RecommendRoutingPlugin:input_type -> looprpc.RecommendRoutingPluginReq
|
||||
30, // 37: looprpc.SwapServer.ReportRoutingResult:input_type -> looprpc.ReportRoutingResultReq
|
||||
32, // 38: looprpc.SwapServer.MuSig2SignSweep:input_type -> looprpc.MuSig2SignSweepReq
|
||||
35, // 39: looprpc.SwapServer.PushKey:input_type -> looprpc.ServerPushKeyReq
|
||||
37, // 40: looprpc.SwapServer.FetchL402:input_type -> looprpc.FetchL402Request
|
||||
10, // 41: looprpc.SwapServer.LoopOutTerms:output_type -> looprpc.ServerLoopOutTerms
|
||||
6, // 42: looprpc.SwapServer.NewLoopOutSwap:output_type -> looprpc.ServerLoopOutResponse
|
||||
18, // 43: looprpc.SwapServer.LoopOutPushPreimage:output_type -> looprpc.ServerLoopOutPushPreimageResponse
|
||||
8, // 44: looprpc.SwapServer.LoopOutQuote:output_type -> looprpc.ServerLoopOutQuote
|
||||
16, // 45: looprpc.SwapServer.LoopInTerms:output_type -> looprpc.ServerLoopInTerms
|
||||
12, // 46: looprpc.SwapServer.NewLoopInSwap:output_type -> looprpc.ServerLoopInResponse
|
||||
14, // 47: looprpc.SwapServer.LoopInQuote:output_type -> looprpc.ServerLoopInQuoteResponse
|
||||
20, // 48: looprpc.SwapServer.SubscribeLoopOutUpdates:output_type -> looprpc.SubscribeLoopOutUpdatesResponse
|
||||
21, // 49: looprpc.SwapServer.SubscribeLoopInUpdates:output_type -> looprpc.SubscribeLoopInUpdatesResponse
|
||||
25, // 50: looprpc.SwapServer.CancelLoopOutSwap:output_type -> looprpc.CancelLoopOutSwapResponse
|
||||
27, // 51: looprpc.SwapServer.Probe:output_type -> looprpc.ServerProbeResponse
|
||||
29, // 52: looprpc.SwapServer.RecommendRoutingPlugin:output_type -> looprpc.RecommendRoutingPluginRes
|
||||
31, // 53: looprpc.SwapServer.ReportRoutingResult:output_type -> looprpc.ReportRoutingResultRes
|
||||
34, // 54: looprpc.SwapServer.MuSig2SignSweep:output_type -> looprpc.MuSig2SignSweepRes
|
||||
36, // 55: looprpc.SwapServer.PushKey:output_type -> looprpc.ServerPushKeyRes
|
||||
38, // 56: looprpc.SwapServer.FetchL402:output_type -> looprpc.FetchL402Response
|
||||
41, // [41:57] is the sub-list for method output_type
|
||||
25, // [25:41] is the sub-list for method input_type
|
||||
25, // [25:25] is the sub-list for extension type_name
|
||||
25, // [25:25] is the sub-list for extension extendee
|
||||
0, // [0:25] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_server_proto_init() }
|
||||
|
|
@ -3607,7 +3704,7 @@ func file_server_proto_init() {
|
|||
}
|
||||
}
|
||||
file_server_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MuSig2SignSweepRes); i {
|
||||
switch v := v.(*PrevoutInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
|
@ -3619,7 +3716,7 @@ func file_server_proto_init() {
|
|||
}
|
||||
}
|
||||
file_server_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerPushKeyReq); i {
|
||||
switch v := v.(*MuSig2SignSweepRes); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
|
@ -3631,7 +3728,7 @@ func file_server_proto_init() {
|
|||
}
|
||||
}
|
||||
file_server_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ServerPushKeyRes); i {
|
||||
switch v := v.(*ServerPushKeyReq); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
|
@ -3643,7 +3740,7 @@ func file_server_proto_init() {
|
|||
}
|
||||
}
|
||||
file_server_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FetchL402Request); i {
|
||||
switch v := v.(*ServerPushKeyRes); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
|
@ -3655,6 +3752,18 @@ func file_server_proto_init() {
|
|||
}
|
||||
}
|
||||
file_server_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FetchL402Request); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_server_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FetchL402Response); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
|
@ -3676,7 +3785,7 @@ func file_server_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_server_proto_rawDesc,
|
||||
NumEnums: 5,
|
||||
NumMessages: 33,
|
||||
NumMessages: 34,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
// We can't change this to swapserverrpc, it would be a breaking change because
|
||||
// the package name is also contained in the HTTP URIs and old clients would
|
||||
// call the wrong endpoints. Luckily with the go_package option we can have
|
||||
// different golang and RPC package names to fix protobuf namespace conflicts.
|
||||
package looprpc;
|
||||
import "common.proto";
|
||||
|
||||
option go_package = "github.com/lightninglabs/loop/swapserverrpc";
|
||||
|
||||
|
|
@ -515,7 +514,6 @@ message ServerProbeRequest {
|
|||
// The protocol version that the client adheres to.
|
||||
ProtocolVersion protocol_version = 1;
|
||||
|
||||
// The probe amount.
|
||||
uint64 amt = 2;
|
||||
|
||||
// The target node for the probe.
|
||||
|
|
@ -598,6 +596,23 @@ message MuSig2SignSweepReq {
|
|||
|
||||
// The psbt of the sweep txn.
|
||||
bytes sweep_tx_psbt = 6;
|
||||
|
||||
// The prevout information of the sweep txn.
|
||||
repeated PrevoutInfo prevout_info = 7;
|
||||
}
|
||||
|
||||
message PrevoutInfo {
|
||||
// The value of the txout.
|
||||
uint64 value = 1;
|
||||
|
||||
// The pk_script of the txout.
|
||||
bytes pk_script = 2;
|
||||
|
||||
// The txid of the txout.
|
||||
bytes txid_bytes = 3;
|
||||
|
||||
// The index of the txout.
|
||||
uint32 output_index = 4;
|
||||
}
|
||||
|
||||
message MuSig2SignSweepRes {
|
||||
|
|
|
|||
30
sweepbatcher/log.go
Normal file
30
sweepbatcher/log.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package sweepbatcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the
|
||||
// caller requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger("SWEEP", nil))
|
||||
}
|
||||
|
||||
// batchPrefixLogger returns a logger that prefixes all log messages with the ID.
|
||||
func batchPrefixLogger(batchID string) btclog.Logger {
|
||||
return build.NewPrefixLog(fmt.Sprintf("[Batch %s]", batchID), log)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
371
sweepbatcher/store.go
Normal file
371
sweepbatcher/store.go
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
package sweepbatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/loopdb/sqlc"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
type BaseDB interface {
|
||||
// ConfirmBatch confirms a batch by setting the state to confirmed.
|
||||
ConfirmBatch(ctx context.Context, id int32) error
|
||||
|
||||
// GetBatchSweeps fetches all the sweeps that are part a batch.
|
||||
GetBatchSweeps(ctx context.Context, batchID int32) (
|
||||
[]sqlc.GetBatchSweepsRow, error)
|
||||
|
||||
// GetSweepStatus returns true if the sweep has been completed.
|
||||
GetSweepStatus(ctx context.Context, swapHash []byte) (bool, error)
|
||||
|
||||
// GetSwapUpdates fetches all the updates for a swap.
|
||||
GetSwapUpdates(ctx context.Context, swapHash []byte) (
|
||||
[]sqlc.SwapUpdate, error)
|
||||
|
||||
// FetchUnconfirmedSweepBatches fetches all the batches from the
|
||||
// database that are not in a confirmed state.
|
||||
GetUnconfirmedBatches(ctx context.Context) ([]sqlc.SweepBatch, error)
|
||||
|
||||
// InsertBatch inserts a batch into the database, returning the id of
|
||||
// the inserted batch.
|
||||
InsertBatch(ctx context.Context, arg sqlc.InsertBatchParams) (
|
||||
int32, error)
|
||||
|
||||
// UpdateBatch updates a batch in the database.
|
||||
UpdateBatch(ctx context.Context, arg sqlc.UpdateBatchParams) error
|
||||
|
||||
// UpsertSweep inserts a sweep into the database, or updates an existing
|
||||
// sweep if it already exists.
|
||||
UpsertSweep(ctx context.Context, arg sqlc.UpsertSweepParams) error
|
||||
|
||||
// ExecTx allows for executing a function in the context of a database
|
||||
// transaction.
|
||||
ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
|
||||
txBody func(*sqlc.Queries) error) error
|
||||
}
|
||||
|
||||
// SQLStore manages the reservations in the database.
|
||||
type SQLStore struct {
|
||||
baseDb BaseDB
|
||||
|
||||
network *chaincfg.Params
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// NewSQLStore creates a new SQLStore.
|
||||
func NewSQLStore(db BaseDB, network *chaincfg.Params) *SQLStore {
|
||||
return &SQLStore{
|
||||
baseDb: db,
|
||||
network: network,
|
||||
clock: clock.NewDefaultClock(),
|
||||
}
|
||||
}
|
||||
|
||||
// FetchUnconfirmedSweepBatches fetches all the batches from the database that
|
||||
// are not in a confirmed state.
|
||||
func (s *SQLStore) FetchUnconfirmedSweepBatches(ctx context.Context) ([]*dbBatch,
|
||||
error) {
|
||||
|
||||
var batches []*dbBatch
|
||||
|
||||
dbBatches, err := s.baseDb.GetUnconfirmedBatches(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, dbBatch := range dbBatches {
|
||||
batch := convertBatchRow(dbBatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batches = append(batches, batch)
|
||||
}
|
||||
|
||||
return batches, err
|
||||
}
|
||||
|
||||
// InsertSweepBatch inserts a batch into the database, returning the id of the
|
||||
// inserted batch.
|
||||
func (s *SQLStore) InsertSweepBatch(ctx context.Context, batch *dbBatch) (int32,
|
||||
error) {
|
||||
|
||||
return s.baseDb.InsertBatch(ctx, batchToInsertArgs(*batch))
|
||||
}
|
||||
|
||||
// UpdateSweepBatch updates a batch in the database.
|
||||
func (s *SQLStore) UpdateSweepBatch(ctx context.Context, batch *dbBatch) error {
|
||||
return s.baseDb.UpdateBatch(ctx, batchToUpdateArgs(*batch))
|
||||
}
|
||||
|
||||
// ConfirmBatch confirms a batch by setting the state to confirmed.
|
||||
func (s *SQLStore) ConfirmBatch(ctx context.Context, id int32) error {
|
||||
return s.baseDb.ConfirmBatch(ctx, id)
|
||||
}
|
||||
|
||||
// FetchBatchSweeps fetches all the sweeps that are part a batch.
|
||||
func (s *SQLStore) FetchBatchSweeps(ctx context.Context, id int32) (
|
||||
[]*dbSweep, error) {
|
||||
|
||||
readOpts := loopdb.NewSqlReadOpts()
|
||||
var sweeps []*dbSweep
|
||||
|
||||
err := s.baseDb.ExecTx(ctx, readOpts, func(tx *sqlc.Queries) error {
|
||||
dbSweeps, err := tx.GetBatchSweeps(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dbSweep := range dbSweeps {
|
||||
updates, err := s.baseDb.GetSwapUpdates(
|
||||
ctx, dbSweep.SwapHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sweep, err := s.convertSweepRow(dbSweep, updates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sweeps = append(sweeps, &sweep)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sweeps, nil
|
||||
}
|
||||
|
||||
// UpsertSweep inserts a sweep into the database, or updates an existing sweep
|
||||
// if it already exists.
|
||||
func (s *SQLStore) UpsertSweep(ctx context.Context, sweep *dbSweep) error {
|
||||
return s.baseDb.UpsertSweep(ctx, sweepToUpsertArgs(*sweep))
|
||||
}
|
||||
|
||||
// GetSweepStatus returns true if the sweep has been completed.
|
||||
func (s *SQLStore) GetSweepStatus(ctx context.Context, swapHash lntypes.Hash) (
|
||||
bool, error) {
|
||||
|
||||
return s.baseDb.GetSweepStatus(ctx, swapHash[:])
|
||||
}
|
||||
|
||||
type dbBatch struct {
|
||||
// ID is the unique identifier of the batch.
|
||||
ID int32
|
||||
|
||||
// State is the current state of the batch.
|
||||
State string
|
||||
|
||||
// BatchTxid is the txid of the batch transaction.
|
||||
BatchTxid chainhash.Hash
|
||||
|
||||
// BatchPkScript is the pkscript of the batch transaction.
|
||||
BatchPkScript []byte
|
||||
|
||||
// LastRbfHeight is the height at which the last RBF attempt was made.
|
||||
LastRbfHeight int32
|
||||
|
||||
// LastRbfSatPerKw is the sat per kw of the last RBF attempt.
|
||||
LastRbfSatPerKw int32
|
||||
|
||||
// MaxTimeoutDistance is the maximum timeout distance of the batch.
|
||||
MaxTimeoutDistance int32
|
||||
}
|
||||
|
||||
type dbSweep struct {
|
||||
// ID is the unique identifier of the sweep.
|
||||
ID int32
|
||||
|
||||
// BatchID is the ID of the batch that the sweep belongs to.
|
||||
BatchID int32
|
||||
|
||||
// SwapHash is the hash of the swap that the sweep belongs to.
|
||||
SwapHash lntypes.Hash
|
||||
|
||||
// Outpoint is the outpoint of the sweep.
|
||||
Outpoint wire.OutPoint
|
||||
|
||||
// Amount is the amount of the sweep.
|
||||
Amount btcutil.Amount
|
||||
|
||||
// Completed indicates whether this sweep is completed.
|
||||
Completed bool
|
||||
|
||||
// LoopOut is the loop out that the sweep belongs to.
|
||||
LoopOut *loopdb.LoopOut
|
||||
}
|
||||
|
||||
// convertBatchRow converts a batch row from db to a sweepbatcher.Batch struct.
|
||||
func convertBatchRow(row sqlc.SweepBatch) *dbBatch {
|
||||
batch := dbBatch{
|
||||
ID: row.ID,
|
||||
}
|
||||
|
||||
if row.Confirmed {
|
||||
batch.State = batchOpen
|
||||
}
|
||||
|
||||
if row.BatchTxID.Valid {
|
||||
err := chainhash.Decode(&batch.BatchTxid, row.BatchTxID.String)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
batch.BatchPkScript = row.BatchPkScript
|
||||
|
||||
if row.LastRbfHeight.Valid {
|
||||
batch.LastRbfHeight = row.LastRbfHeight.Int32
|
||||
}
|
||||
|
||||
if row.LastRbfSatPerKw.Valid {
|
||||
batch.LastRbfSatPerKw = row.LastRbfSatPerKw.Int32
|
||||
}
|
||||
|
||||
batch.MaxTimeoutDistance = row.MaxTimeoutDistance
|
||||
|
||||
return &batch
|
||||
}
|
||||
|
||||
// BatchToUpsertArgs converts a Batch struct to the arguments needed to insert
|
||||
// it into the database.
|
||||
func batchToInsertArgs(batch dbBatch) sqlc.InsertBatchParams {
|
||||
args := sqlc.InsertBatchParams{
|
||||
Confirmed: false,
|
||||
BatchTxID: sql.NullString{
|
||||
Valid: true,
|
||||
String: batch.BatchTxid.String(),
|
||||
},
|
||||
BatchPkScript: batch.BatchPkScript,
|
||||
LastRbfHeight: sql.NullInt32{
|
||||
Valid: true,
|
||||
Int32: batch.LastRbfHeight,
|
||||
},
|
||||
LastRbfSatPerKw: sql.NullInt32{
|
||||
Valid: true,
|
||||
Int32: batch.LastRbfSatPerKw,
|
||||
},
|
||||
MaxTimeoutDistance: batch.MaxTimeoutDistance,
|
||||
}
|
||||
|
||||
if batch.State == batchConfirmed {
|
||||
args.Confirmed = true
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// BatchToUpsertArgs converts a Batch struct to the arguments needed to insert
|
||||
// it into the database.
|
||||
func batchToUpdateArgs(batch dbBatch) sqlc.UpdateBatchParams {
|
||||
args := sqlc.UpdateBatchParams{
|
||||
ID: batch.ID,
|
||||
Confirmed: false,
|
||||
BatchTxID: sql.NullString{
|
||||
Valid: true,
|
||||
String: batch.BatchTxid.String(),
|
||||
},
|
||||
BatchPkScript: batch.BatchPkScript,
|
||||
LastRbfHeight: sql.NullInt32{
|
||||
Valid: true,
|
||||
Int32: batch.LastRbfHeight,
|
||||
},
|
||||
LastRbfSatPerKw: sql.NullInt32{
|
||||
Valid: true,
|
||||
Int32: batch.LastRbfSatPerKw,
|
||||
},
|
||||
}
|
||||
|
||||
if batch.State == batchConfirmed {
|
||||
args.Confirmed = true
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// convertSweepRow converts a sweep row from db to a sweep struct.
|
||||
func (s *SQLStore) convertSweepRow(row sqlc.GetBatchSweepsRow,
|
||||
updates []sqlc.SwapUpdate) (dbSweep, error) {
|
||||
|
||||
sweep := dbSweep{
|
||||
ID: row.ID,
|
||||
BatchID: row.BatchID,
|
||||
Amount: btcutil.Amount(row.Amt),
|
||||
}
|
||||
|
||||
swapHash, err := lntypes.MakeHash(row.SwapHash)
|
||||
if err != nil {
|
||||
return sweep, err
|
||||
}
|
||||
|
||||
sweep.SwapHash = swapHash
|
||||
|
||||
hash, err := chainhash.NewHash(row.OutpointTxid)
|
||||
if err != nil {
|
||||
return sweep, err
|
||||
}
|
||||
|
||||
sweep.Outpoint = wire.OutPoint{
|
||||
Hash: *hash,
|
||||
Index: uint32(row.OutpointIndex),
|
||||
}
|
||||
|
||||
sweep.LoopOut, err = loopdb.ConvertLoopOutRow(
|
||||
s.network,
|
||||
sqlc.GetLoopOutSwapRow{
|
||||
ID: row.ID,
|
||||
SwapHash: row.SwapHash,
|
||||
Preimage: row.Preimage,
|
||||
InitiationTime: row.InitiationTime,
|
||||
AmountRequested: row.AmountRequested,
|
||||
CltvExpiry: row.CltvExpiry,
|
||||
MaxMinerFee: row.MaxMinerFee,
|
||||
MaxSwapFee: row.MaxSwapFee,
|
||||
InitiationHeight: row.InitiationHeight,
|
||||
ProtocolVersion: row.ProtocolVersion,
|
||||
Label: row.Label,
|
||||
DestAddress: row.DestAddress,
|
||||
SwapInvoice: row.SwapInvoice,
|
||||
MaxSwapRoutingFee: row.MaxSwapRoutingFee,
|
||||
SweepConfTarget: row.SweepConfTarget,
|
||||
HtlcConfirmations: row.HtlcConfirmations,
|
||||
OutgoingChanSet: row.OutgoingChanSet,
|
||||
PrepayInvoice: row.PrepayInvoice,
|
||||
MaxPrepayRoutingFee: row.MaxPrepayRoutingFee,
|
||||
PublicationDeadline: row.PublicationDeadline,
|
||||
SingleSweep: row.SingleSweep,
|
||||
SenderScriptPubkey: row.SenderScriptPubkey,
|
||||
ReceiverScriptPubkey: row.ReceiverScriptPubkey,
|
||||
SenderInternalPubkey: row.SenderInternalPubkey,
|
||||
ReceiverInternalPubkey: row.ReceiverInternalPubkey,
|
||||
ClientKeyFamily: row.ClientKeyFamily,
|
||||
ClientKeyIndex: row.ClientKeyIndex,
|
||||
}, updates,
|
||||
)
|
||||
|
||||
return sweep, err
|
||||
}
|
||||
|
||||
// sweepToUpsertArgs converts a Sweep struct to the arguments needed to insert.
|
||||
func sweepToUpsertArgs(sweep dbSweep) sqlc.UpsertSweepParams {
|
||||
return sqlc.UpsertSweepParams{
|
||||
SwapHash: sweep.SwapHash[:],
|
||||
BatchID: sweep.BatchID,
|
||||
OutpointTxid: sweep.Outpoint.Hash[:],
|
||||
OutpointIndex: int32(sweep.Outpoint.Index),
|
||||
Amt: int64(sweep.Amount),
|
||||
Completed: sweep.Completed,
|
||||
}
|
||||
}
|
||||
125
sweepbatcher/store_mock.go
Normal file
125
sweepbatcher/store_mock.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package sweepbatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
// StoreMock implements a mock client swap store.
|
||||
type StoreMock struct {
|
||||
batches map[int32]dbBatch
|
||||
sweeps map[lntypes.Hash]dbSweep
|
||||
}
|
||||
|
||||
// NewStoreMock instantiates a new mock store.
|
||||
func NewStoreMock() *StoreMock {
|
||||
return &StoreMock{
|
||||
batches: make(map[int32]dbBatch),
|
||||
sweeps: make(map[lntypes.Hash]dbSweep),
|
||||
}
|
||||
}
|
||||
|
||||
// FetchUnconfirmedBatches fetches all the loop out sweep batches from the
|
||||
// database that are not in a confirmed state.
|
||||
func (s *StoreMock) FetchUnconfirmedSweepBatches(ctx context.Context) (
|
||||
[]*dbBatch, error) {
|
||||
|
||||
result := []*dbBatch{}
|
||||
for _, batch := range s.batches {
|
||||
batch := batch
|
||||
if batch.State != "confirmed" {
|
||||
result = append(result, &batch)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// InsertSweepBatch inserts a batch into the database, returning the id of the
|
||||
// inserted batch.
|
||||
func (s *StoreMock) InsertSweepBatch(ctx context.Context,
|
||||
batch *dbBatch) (int32, error) {
|
||||
|
||||
var id int32
|
||||
|
||||
if len(s.batches) == 0 {
|
||||
id = 0
|
||||
} else {
|
||||
id = int32(len(s.batches))
|
||||
}
|
||||
|
||||
s.batches[id] = *batch
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// UpdateSweepBatch updates a batch in the database.
|
||||
func (s *StoreMock) UpdateSweepBatch(ctx context.Context,
|
||||
batch *dbBatch) error {
|
||||
|
||||
s.batches[batch.ID] = *batch
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfirmBatch confirms a batch.
|
||||
func (s *StoreMock) ConfirmBatch(ctx context.Context, id int32) error {
|
||||
batch, ok := s.batches[id]
|
||||
if !ok {
|
||||
return errors.New("batch not found")
|
||||
}
|
||||
|
||||
batch.State = "confirmed"
|
||||
s.batches[batch.ID] = batch
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchBatchSweeps fetches all the sweeps that belong to a batch.
|
||||
func (s *StoreMock) FetchBatchSweeps(ctx context.Context,
|
||||
id int32) ([]*dbSweep, error) {
|
||||
|
||||
result := []*dbSweep{}
|
||||
for _, sweep := range s.sweeps {
|
||||
sweep := sweep
|
||||
if sweep.BatchID == id {
|
||||
result = append(result, &sweep)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].ID < result[j].ID
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UpsertSweep inserts a sweep into the database, or updates an existing sweep.
|
||||
func (s *StoreMock) UpsertSweep(ctx context.Context, sweep *dbSweep) error {
|
||||
s.sweeps[sweep.SwapHash] = *sweep
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSweepStatus returns the status of a sweep.
|
||||
func (s *StoreMock) GetSweepStatus(ctx context.Context,
|
||||
swapHash lntypes.Hash) (bool, error) {
|
||||
|
||||
sweep, ok := s.sweeps[swapHash]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return sweep.Completed, nil
|
||||
}
|
||||
|
||||
// Close closes the store.
|
||||
func (s *StoreMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertSweepStored asserts that a sweep is stored.
|
||||
func (s *StoreMock) AssertSweepStored(id lntypes.Hash) bool {
|
||||
_, ok := s.sweeps[id]
|
||||
return ok
|
||||
}
|
||||
1382
sweepbatcher/sweep_batch.go
Normal file
1382
sweepbatcher/sweep_batch.go
Normal file
File diff suppressed because it is too large
Load diff
657
sweepbatcher/sweep_batcher.go
Normal file
657
sweepbatcher/sweep_batcher.go
Normal file
|
|
@ -0,0 +1,657 @@
|
|||
package sweepbatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/utils"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultMaxTimeoutDistance is the default maximum timeout distance
|
||||
// of sweeps that can appear in the same batch.
|
||||
defaultMaxTimeoutDistance = 288
|
||||
|
||||
// batchOpen is the string representation of the state of a batch that
|
||||
// is open.
|
||||
batchOpen = "open"
|
||||
|
||||
// batchClosed is the string representation of the state of a batch
|
||||
// that is closed.
|
||||
batchClosed = "closed"
|
||||
|
||||
// batchConfirmed is the string representation of the state of a batch
|
||||
// that is confirmed.
|
||||
batchConfirmed = "confirmed"
|
||||
|
||||
// defaultMainnetPublishDelay is the default publish delay that is used
|
||||
// for mainnet.
|
||||
defaultMainnetPublishDelay = 5 * time.Second
|
||||
|
||||
// defaultTestnetPublishDelay is the default publish delay that is used
|
||||
// for testnet.
|
||||
defaultPublishDelay = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
type BatcherStore interface {
|
||||
// FetchUnconfirmedSweepBatches fetches all the batches from the
|
||||
// database that are not in a confirmed state.
|
||||
FetchUnconfirmedSweepBatches(ctx context.Context) ([]*dbBatch,
|
||||
error)
|
||||
|
||||
// InsertSweepBatch inserts a batch into the database, returning the id
|
||||
// of the inserted batch.
|
||||
InsertSweepBatch(ctx context.Context,
|
||||
batch *dbBatch) (int32, error)
|
||||
|
||||
// UpdateSweepBatch updates a batch in the database.
|
||||
UpdateSweepBatch(ctx context.Context,
|
||||
batch *dbBatch) error
|
||||
|
||||
// ConfirmBatch confirms a batch by setting its state to confirmed.
|
||||
ConfirmBatch(ctx context.Context, id int32) error
|
||||
|
||||
// FetchBatchSweeps fetches all the sweeps that belong to a batch.
|
||||
FetchBatchSweeps(ctx context.Context,
|
||||
id int32) ([]*dbSweep, error)
|
||||
|
||||
// UpsertSweep inserts a sweep into the database, or updates an existing
|
||||
// sweep if it already exists.
|
||||
UpsertSweep(ctx context.Context, sweep *dbSweep) error
|
||||
|
||||
// GetSweepStatus returns the completed status of the sweep.
|
||||
GetSweepStatus(ctx context.Context, swapHash lntypes.Hash) (
|
||||
bool, error)
|
||||
}
|
||||
|
||||
// MuSig2SignSweep is a function that can be used to sign a sweep transaction
|
||||
// cooperatively with the swap server.
|
||||
type MuSig2SignSweep func(ctx context.Context,
|
||||
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
|
||||
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
|
||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error)
|
||||
|
||||
// VerifySchnorrSig is a function that can be used to verify a schnorr
|
||||
// signature.
|
||||
type VerifySchnorrSig func(pubKey *btcec.PublicKey, hash, sig []byte) error
|
||||
|
||||
// SweepRequest is a request to sweep a specific outpoint.
|
||||
type SweepRequest struct {
|
||||
// SwapHash is the hash of the swap that is being swept.
|
||||
SwapHash lntypes.Hash
|
||||
|
||||
// Outpoint is the outpoint that is being swept.
|
||||
Outpoint wire.OutPoint
|
||||
|
||||
// Value is the value of the outpoint that is being swept.
|
||||
Value btcutil.Amount
|
||||
|
||||
// Notifier is a notifier that is used to notify the requester of this
|
||||
// sweep that the sweep was successful.
|
||||
Notifier *SpendNotifier
|
||||
}
|
||||
|
||||
// SpendNotifier is a notifier that is used to notify the requester of a sweep
|
||||
// that the sweep was successful.
|
||||
type SpendNotifier struct {
|
||||
// SpendChan is a channel where the spend details are received.
|
||||
SpendChan chan *wire.MsgTx
|
||||
|
||||
// SpendErrChan is a channel where spend errors are received.
|
||||
SpendErrChan chan error
|
||||
|
||||
// QuitChan is a channel that can be closed to stop the notifier.
|
||||
QuitChan chan bool
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBatcherShuttingDown = fmt.Errorf("batcher shutting down")
|
||||
)
|
||||
|
||||
// Batcher is a system that is responsible for accepting sweep requests and
|
||||
// placing them in appropriate batches. It will spin up new batches as needed.
|
||||
type Batcher struct {
|
||||
// batches is a map of batch IDs to the currently active batches.
|
||||
batches map[int32]*batch
|
||||
|
||||
// sweepReqs is a channel where sweep requests are received.
|
||||
sweepReqs chan SweepRequest
|
||||
|
||||
// errChan is a channel where errors are received.
|
||||
errChan chan error
|
||||
|
||||
// quit signals that the batch must stop.
|
||||
quit chan struct{}
|
||||
|
||||
// wallet is the wallet kit client that is used by batches.
|
||||
wallet lndclient.WalletKitClient
|
||||
|
||||
// chainNotifier is the chain notifier client that is used by batches.
|
||||
chainNotifier lndclient.ChainNotifierClient
|
||||
|
||||
// signerClient is the signer client that is used by batches.
|
||||
signerClient lndclient.SignerClient
|
||||
|
||||
// musig2ServerKit includes all the required functionality to collect
|
||||
// and verify signatures by the swap server in order to cooperatively
|
||||
// sweep funds.
|
||||
musig2ServerSign MuSig2SignSweep
|
||||
|
||||
// verifySchnorrSig is a function that can be used to verify a schnorr
|
||||
// signature.
|
||||
VerifySchnorrSig VerifySchnorrSig
|
||||
|
||||
// chainParams are the chain parameters of the chain that is used by
|
||||
// batches.
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
// store includes all the database interactions that are needed by the
|
||||
// batcher and the batches.
|
||||
store BatcherStore
|
||||
|
||||
// swapStore includes all the database interactions that are needed for
|
||||
// interacting with swaps.
|
||||
swapStore loopdb.SwapStore
|
||||
|
||||
// wg is a waitgroup that is used to wait for all the goroutines to
|
||||
// exit.
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewBatcher creates a new Batcher instance.
|
||||
func NewBatcher(wallet lndclient.WalletKitClient,
|
||||
chainNotifier lndclient.ChainNotifierClient,
|
||||
signerClient lndclient.SignerClient, musig2ServerSigner MuSig2SignSweep,
|
||||
verifySchnorrSig VerifySchnorrSig, chainparams *chaincfg.Params,
|
||||
store BatcherStore, swapStore loopdb.SwapStore) *Batcher {
|
||||
|
||||
return &Batcher{
|
||||
batches: make(map[int32]*batch),
|
||||
sweepReqs: make(chan SweepRequest),
|
||||
errChan: make(chan error, 1),
|
||||
quit: make(chan struct{}),
|
||||
wallet: wallet,
|
||||
chainNotifier: chainNotifier,
|
||||
signerClient: signerClient,
|
||||
musig2ServerSign: musig2ServerSigner,
|
||||
VerifySchnorrSig: verifySchnorrSig,
|
||||
chainParams: chainparams,
|
||||
store: store,
|
||||
swapStore: swapStore,
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the batcher and processes incoming sweep requests.
|
||||
func (b *Batcher) Run(ctx context.Context) error {
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
cancel()
|
||||
|
||||
for _, batch := range b.batches {
|
||||
batch.Wait()
|
||||
}
|
||||
|
||||
b.wg.Wait()
|
||||
}()
|
||||
|
||||
// First we fetch all the batches that are not in a confirmed state from
|
||||
// the database. We will then resume the execution of these batches.
|
||||
batches, err := b.FetchUnconfirmedBatches(runCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, batch := range batches {
|
||||
err := b.spinUpBatchFromDB(runCtx, batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case sweepReq := <-b.sweepReqs:
|
||||
sweep, err := b.fetchSweep(runCtx, sweepReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.handleSweep(runCtx, sweep, sweepReq.Notifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case err := <-b.errChan:
|
||||
return err
|
||||
|
||||
case <-runCtx.Done():
|
||||
return runCtx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddSweep adds a sweep request to the batcher for handling. This will either
|
||||
// place the sweep in an existing batch or create a new one.
|
||||
func (b *Batcher) AddSweep(sweepReq *SweepRequest) error {
|
||||
select {
|
||||
case b.sweepReqs <- *sweepReq:
|
||||
return nil
|
||||
|
||||
case <-b.quit:
|
||||
return ErrBatcherShuttingDown
|
||||
}
|
||||
}
|
||||
|
||||
// handleSweep handles a sweep request by either placing it in an existing
|
||||
// batch, or by spinning up a new batch for it.
|
||||
func (b *Batcher) handleSweep(ctx context.Context, sweep *sweep,
|
||||
notifier *SpendNotifier) error {
|
||||
|
||||
completed, err := b.store.GetSweepStatus(ctx, sweep.swapHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Batcher handling sweep %x, completed=%v", sweep.swapHash[:6],
|
||||
completed)
|
||||
|
||||
// If the sweep has already been completed in a confirmed batch then we
|
||||
// can't attach its notifier to the batch as that is no longer running.
|
||||
// Instead we directly detect and return the spend here.
|
||||
if completed && *notifier != (SpendNotifier{}) {
|
||||
go b.monitorSpendAndNotify(ctx, sweep, notifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
sweep.notifier = notifier
|
||||
|
||||
// Check if the sweep is already in a batch. If that is the case, we
|
||||
// provide the sweep to that batch and return.
|
||||
for _, batch := range b.batches {
|
||||
// This is a check to see if a batch is completed. In that case
|
||||
// we just lazily delete it and continue our scan.
|
||||
if batch.isComplete() {
|
||||
delete(b.batches, batch.id)
|
||||
continue
|
||||
}
|
||||
|
||||
if batch.sweepExists(sweep.swapHash) {
|
||||
accepted, err := batch.addSweep(ctx, sweep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !accepted {
|
||||
return fmt.Errorf("existing sweep %x was not "+
|
||||
"accepted by batch %d", sweep.swapHash[:6],
|
||||
batch.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the batches accepts the sweep, we provide it to that batch.
|
||||
for _, batch := range b.batches {
|
||||
accepted, err := batch.addSweep(ctx, sweep)
|
||||
if err != nil && err != ErrBatchShuttingDown {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the sweep was accepted by this batch, we return, our job
|
||||
// is done.
|
||||
if accepted {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If no batch is capable of accepting the sweep, we spin up a fresh
|
||||
// batch and hand the sweep over to it.
|
||||
batch, err := b.spinUpBatch(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the sweep to the fresh batch.
|
||||
accepted, err := batch.addSweep(ctx, sweep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the sweep wasn't accepted by the fresh batch something is wrong,
|
||||
// we should return the error.
|
||||
if !accepted {
|
||||
return fmt.Errorf("sweep %x was not accepted by new batch %d",
|
||||
sweep.swapHash[:6], batch.id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// spinUpBatch spins up a new batch and returns it.
|
||||
func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
|
||||
cfg := batchConfig{
|
||||
maxTimeoutDistance: defaultMaxTimeoutDistance,
|
||||
batchConfTarget: defaultBatchConfTarget,
|
||||
}
|
||||
|
||||
switch b.chainParams {
|
||||
case &chaincfg.MainNetParams:
|
||||
cfg.batchPublishDelay = defaultMainnetPublishDelay
|
||||
|
||||
default:
|
||||
cfg.batchPublishDelay = defaultPublishDelay
|
||||
}
|
||||
|
||||
batchKit := batchKit{
|
||||
returnChan: b.sweepReqs,
|
||||
wallet: b.wallet,
|
||||
chainNotifier: b.chainNotifier,
|
||||
signerClient: b.signerClient,
|
||||
musig2SignSweep: b.musig2ServerSign,
|
||||
verifySchnorrSig: b.VerifySchnorrSig,
|
||||
purger: b.AddSweep,
|
||||
store: b.store,
|
||||
}
|
||||
|
||||
batch := NewBatch(cfg, batchKit)
|
||||
|
||||
id, err := batch.insertAndAcquireID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We add the batch to our map of batches and start it.
|
||||
b.batches[id] = batch
|
||||
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
|
||||
err := batch.Run(ctx)
|
||||
if err != nil {
|
||||
_ = b.writeToErrChan(ctx, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return batch, nil
|
||||
}
|
||||
|
||||
// spinUpBatchDB spins up a batch that already existed in storage, then
|
||||
// returns it.
|
||||
func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
|
||||
cfg := batchConfig{
|
||||
maxTimeoutDistance: batch.cfg.maxTimeoutDistance,
|
||||
batchConfTarget: defaultBatchConfTarget,
|
||||
}
|
||||
|
||||
rbfCache := rbfCache{
|
||||
LastHeight: batch.rbfCache.LastHeight,
|
||||
FeeRate: batch.rbfCache.FeeRate,
|
||||
}
|
||||
|
||||
dbSweeps, err := b.store.FetchBatchSweeps(ctx, batch.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dbSweeps) == 0 {
|
||||
return fmt.Errorf("batch %d has no sweeps", batch.id)
|
||||
}
|
||||
|
||||
primarySweep := dbSweeps[0]
|
||||
|
||||
sweeps := make(map[lntypes.Hash]sweep)
|
||||
|
||||
for _, dbSweep := range dbSweeps {
|
||||
sweep, err := b.convertSweep(dbSweep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sweeps[sweep.swapHash] = *sweep
|
||||
}
|
||||
|
||||
batchKit := batchKit{
|
||||
id: batch.id,
|
||||
batchTxid: batch.batchTxid,
|
||||
batchPkScript: batch.batchPkScript,
|
||||
state: batch.state,
|
||||
primaryID: primarySweep.SwapHash,
|
||||
sweeps: sweeps,
|
||||
rbfCache: rbfCache,
|
||||
returnChan: b.sweepReqs,
|
||||
wallet: b.wallet,
|
||||
chainNotifier: b.chainNotifier,
|
||||
signerClient: b.signerClient,
|
||||
musig2SignSweep: b.musig2ServerSign,
|
||||
verifySchnorrSig: b.VerifySchnorrSig,
|
||||
purger: b.AddSweep,
|
||||
store: b.store,
|
||||
log: batchPrefixLogger(fmt.Sprintf("%d", batch.id)),
|
||||
}
|
||||
|
||||
newBatch := NewBatchFromDB(cfg, batchKit)
|
||||
|
||||
// We add the batch to our map of batches and start it.
|
||||
b.batches[batch.id] = newBatch
|
||||
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
|
||||
err := newBatch.Run(ctx)
|
||||
if err != nil {
|
||||
_ = b.writeToErrChan(ctx, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchUnconfirmedBatches fetches all the batches from the database that are
|
||||
// not in a confirmed state.
|
||||
func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
|
||||
error) {
|
||||
|
||||
dbBatches, err := b.store.FetchUnconfirmedSweepBatches(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batches := make([]*batch, 0, len(dbBatches))
|
||||
for _, bch := range dbBatches {
|
||||
bch := bch
|
||||
|
||||
batch := batch{}
|
||||
batch.id = bch.ID
|
||||
|
||||
switch bch.State {
|
||||
case batchOpen:
|
||||
batch.state = Open
|
||||
|
||||
case batchClosed:
|
||||
batch.state = Closed
|
||||
|
||||
case batchConfirmed:
|
||||
batch.state = Confirmed
|
||||
}
|
||||
|
||||
batch.batchTxid = &bch.BatchTxid
|
||||
batch.batchPkScript = bch.BatchPkScript
|
||||
|
||||
rbfCache := rbfCache{
|
||||
LastHeight: bch.LastRbfHeight,
|
||||
FeeRate: chainfee.SatPerKWeight(bch.LastRbfSatPerKw),
|
||||
}
|
||||
batch.rbfCache = rbfCache
|
||||
|
||||
bchCfg := batchConfig{
|
||||
maxTimeoutDistance: bch.MaxTimeoutDistance,
|
||||
}
|
||||
batch.cfg = &bchCfg
|
||||
|
||||
batches = append(batches, &batch)
|
||||
}
|
||||
|
||||
return batches, nil
|
||||
}
|
||||
|
||||
// monitorSpendAndNotify monitors the spend of a specific outpoint and writes
|
||||
// the response back to the response channel.
|
||||
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
|
||||
notifier *SpendNotifier) {
|
||||
|
||||
b.wg.Add(1)
|
||||
defer b.wg.Done()
|
||||
|
||||
spendCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn(
|
||||
spendCtx, &sweep.outpoint, sweep.htlc.PkScript,
|
||||
sweep.initiationHeight,
|
||||
)
|
||||
if err != nil {
|
||||
select {
|
||||
case notifier.SpendErrChan <- err:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
_ = b.writeToErrChan(ctx, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Batcher monitoring spend for swap %x", sweep.swapHash[:6])
|
||||
|
||||
for {
|
||||
select {
|
||||
case spend := <-spendChan:
|
||||
select {
|
||||
case notifier.SpendChan <- spend.SpendingTx:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case err := <-spendErr:
|
||||
select {
|
||||
case notifier.SpendErrChan <- err:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
_ = b.writeToErrChan(ctx, err)
|
||||
return
|
||||
|
||||
case <-notifier.QuitChan:
|
||||
return
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Batcher) writeToErrChan(ctx context.Context, err error) error {
|
||||
select {
|
||||
case b.errChan <- err:
|
||||
return nil
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// convertSweep converts a fetched sweep from the database to a sweep that is
|
||||
// ready to be processed by the batcher.
|
||||
func (b *Batcher) convertSweep(dbSweep *dbSweep) (*sweep, error) {
|
||||
swap := dbSweep.LoopOut
|
||||
|
||||
htlc, err := utils.GetHtlc(
|
||||
dbSweep.SwapHash, &swap.Contract.SwapContract, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
swapPaymentAddr, err := utils.ObtainSwapPaymentAddr(
|
||||
swap.Contract.SwapInvoice, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sweep{
|
||||
swapHash: swap.Hash,
|
||||
outpoint: dbSweep.Outpoint,
|
||||
value: dbSweep.Amount,
|
||||
confTarget: swap.Contract.SweepConfTarget,
|
||||
timeout: swap.Contract.CltvExpiry,
|
||||
initiationHeight: swap.Contract.InitiationHeight,
|
||||
htlc: *htlc,
|
||||
preimage: swap.Contract.Preimage,
|
||||
swapInvoicePaymentAddr: *swapPaymentAddr,
|
||||
htlcKeys: swap.Contract.HtlcKeys,
|
||||
htlcSuccessEstimator: htlc.AddSuccessToEstimator,
|
||||
protocolVersion: swap.Contract.ProtocolVersion,
|
||||
isExternalAddr: swap.Contract.IsExternalAddr,
|
||||
destAddr: swap.Contract.DestAddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fetchSweep fetches the sweep related information from the database.
|
||||
func (b *Batcher) fetchSweep(ctx context.Context,
|
||||
sweepReq SweepRequest) (*sweep, error) {
|
||||
|
||||
swapHash, err := lntypes.MakeHash(sweepReq.SwapHash[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse swapHash: %v", err)
|
||||
}
|
||||
|
||||
swap, err := b.swapStore.FetchLoopOutSwap(ctx, swapHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch loop out for %x: %v",
|
||||
swapHash[:6], err)
|
||||
}
|
||||
|
||||
htlc, err := utils.GetHtlc(
|
||||
swapHash, &swap.Contract.SwapContract, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get htlc: %v", err)
|
||||
}
|
||||
|
||||
swapPaymentAddr, err := utils.ObtainSwapPaymentAddr(
|
||||
swap.Contract.SwapInvoice, b.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get payment addr: %v", err)
|
||||
}
|
||||
|
||||
return &sweep{
|
||||
swapHash: swap.Hash,
|
||||
outpoint: sweepReq.Outpoint,
|
||||
value: sweepReq.Value,
|
||||
confTarget: swap.Contract.SweepConfTarget,
|
||||
timeout: swap.Contract.CltvExpiry,
|
||||
initiationHeight: swap.Contract.InitiationHeight,
|
||||
htlc: *htlc,
|
||||
preimage: swap.Contract.Preimage,
|
||||
swapInvoicePaymentAddr: *swapPaymentAddr,
|
||||
htlcKeys: swap.Contract.HtlcKeys,
|
||||
htlcSuccessEstimator: htlc.AddSuccessToEstimator,
|
||||
protocolVersion: swap.Contract.ProtocolVersion,
|
||||
isExternalAddr: swap.Contract.IsExternalAddr,
|
||||
destAddr: swap.Contract.DestAddr,
|
||||
}, nil
|
||||
}
|
||||
986
sweepbatcher/sweep_batcher_test.go
Normal file
986
sweepbatcher/sweep_batcher_test.go
Normal file
|
|
@ -0,0 +1,986 @@
|
|||
package sweepbatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
swapInvoice = "lntb1230n1pjjszzgpp5j76f03wrkya4sm4gxv6az5nmz5aqsvmn4" +
|
||||
"tpguu2sdvdyygedqjgqdq9xyerxcqzzsxqr23ssp5rwzmwtfjmsgranfk8sr" +
|
||||
"4p4gcgmvyd42uug8pxteg2mkk23ndvkqs9qyyssq44ruk3ex59cmv4dm6k4v" +
|
||||
"0kc6c0gcqjs0gkljfyd6c6uatqa2f67xlx3pcg5tnvcae5p3jju8ra77e87d" +
|
||||
"vhhs0jrx53wnc0fq9rkrhmqqelyx7l"
|
||||
|
||||
eventuallyCheckFrequency = 100 * time.Millisecond
|
||||
|
||||
ntfnBufferSize = 1024
|
||||
)
|
||||
|
||||
func testMuSig2SignSweep(ctx context.Context,
|
||||
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
|
||||
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
|
||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var dummyNotifier = SpendNotifier{
|
||||
SpendChan: make(chan *wire.MsgTx, ntfnBufferSize),
|
||||
SpendErrChan: make(chan error, ntfnBufferSize),
|
||||
QuitChan: make(chan bool, ntfnBufferSize),
|
||||
}
|
||||
|
||||
// TestSweepBatcherBatchCreation tests that sweep requests enter the expected
|
||||
// batch based on their timeout distance.
|
||||
func TestSweepBatcherBatchCreation(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
batcherStore := NewStoreMock()
|
||||
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
|
||||
go func() {
|
||||
err := batcher.Run(ctx)
|
||||
if !strings.Contains(err.Error(), "context canceled") {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a sweep request.
|
||||
sweepReq1 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap1 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
},
|
||||
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err := store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Deliver sweep request to batcher.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Insert the same swap twice, this should be a noop.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Once batcher receives sweep request it will eventually spin up a
|
||||
// batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Create a second sweep request that has a timeout distance less than
|
||||
// our configured threshold.
|
||||
sweepReq2 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{2, 2, 2},
|
||||
Value: 222,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{2, 2},
|
||||
Index: 2,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap2 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance - 1,
|
||||
AmountRequested: 222,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
batcher.sweepReqs <- sweepReq2
|
||||
|
||||
// Batcher should not create a second batch as timeout distance is small
|
||||
// enough.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Create a third sweep request that has more timeout distance than
|
||||
// the default.
|
||||
sweepReq3 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{3, 3, 3},
|
||||
Value: 333,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{3, 3},
|
||||
Index: 3,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap3 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance + 1,
|
||||
AmountRequested: 333,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
batcher.sweepReqs <- sweepReq3
|
||||
|
||||
// Batcher should create a second batch as timeout distance is greater
|
||||
// than the threshold
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 2
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since the second batch got created we check that it registered its
|
||||
// primary sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
// Verify that each batch has the correct number of sweeps in it.
|
||||
for _, batch := range batcher.batches {
|
||||
switch batch.primarySweepID {
|
||||
case sweepReq1.SwapHash:
|
||||
if len(batch.sweeps) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq3.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Check that all sweeps were stored.
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq1.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq2.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq3.SwapHash))
|
||||
}
|
||||
|
||||
// TestSweepBatcherSimpleLifecycle tests the simple lifecycle of the batches
|
||||
// that are created and run by the batcher.
|
||||
func TestSweepBatcherSimpleLifecycle(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
batcherStore := NewStoreMock()
|
||||
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
|
||||
go func() {
|
||||
err := batcher.Run(ctx)
|
||||
if !strings.Contains(err.Error(), "context canceled") {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a sweep request.
|
||||
sweepReq1 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap1 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
SweepConfTarget: 111,
|
||||
}
|
||||
|
||||
err := store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Deliver sweep request to batcher.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Eventually request will be consumed and a new batch will spin up.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// When batch is successfully created it will execute it's first step,
|
||||
// which leads to a spend monitor of the primary sweep.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Find the batch and assign it to a local variable for easier access.
|
||||
batch := &batch{}
|
||||
for _, btch := range batcher.batches {
|
||||
if btch.primarySweepID == sweepReq1.SwapHash {
|
||||
batch = btch
|
||||
}
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
// Batch should have the sweep stored.
|
||||
return len(batch.sweeps) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// The primary sweep id should be that of the first inserted sweep.
|
||||
require.Equal(t, batch.primarySweepID, sweepReq1.SwapHash)
|
||||
|
||||
err = lnd.NotifyHeight(601)
|
||||
require.NoError(t, err)
|
||||
|
||||
// After receiving a height notification the batch will step again,
|
||||
// leading to a new spend monitoring.
|
||||
require.Eventually(t, func() bool {
|
||||
return batch.currentHeight == 601
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Create the spending tx that will trigger the spend monitor of the
|
||||
// batch.
|
||||
spendingTx := &wire.MsgTx{
|
||||
Version: 1,
|
||||
// Since the spend monitor is registered on the primary sweep's
|
||||
// outpoint we insert that outpoint here.
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: sweepReq1.Outpoint,
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
PkScript: []byte{3, 2, 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
spendingTxHash := spendingTx.TxHash()
|
||||
|
||||
// Mock the spend notification that spends the swap.
|
||||
spendDetail := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &sweepReq1.Outpoint,
|
||||
SpendingTx: spendingTx,
|
||||
SpenderTxHash: &spendingTxHash,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: 601,
|
||||
}
|
||||
|
||||
// We notify the spend.
|
||||
lnd.SpendChannel <- spendDetail
|
||||
|
||||
// After receiving the spend, the batch is now monitoring for confs.
|
||||
<-lnd.RegisterConfChannel
|
||||
|
||||
// The batch should eventually read the spend notification and progress
|
||||
// its state to closed.
|
||||
require.Eventually(t, func() bool {
|
||||
return batch.state == Closed
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
err = lnd.NotifyHeight(604)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We mock the tx confirmation notification.
|
||||
lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: spendingTx,
|
||||
}
|
||||
|
||||
// Eventually the batch receives the confirmation notification and
|
||||
// confirms itself.
|
||||
require.Eventually(t, func() bool {
|
||||
return batch.isComplete()
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
}
|
||||
|
||||
// TestSweepBatcherSweepReentry tests that when an old version of the batch tx
|
||||
// gets confirmed the sweep leftovers are sent back to the batcher.
|
||||
func TestSweepBatcherSweepReentry(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
batcherStore := NewStoreMock()
|
||||
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
|
||||
go func() {
|
||||
err := batcher.Run(ctx)
|
||||
if !strings.Contains(err.Error(), "context canceled") {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create some sweep requests with timeouts not too far away, in order
|
||||
// to enter the same batch.
|
||||
sweepReq1 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap1 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
SweepConfTarget: 111,
|
||||
}
|
||||
|
||||
err := store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
sweepReq2 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{2, 2, 2},
|
||||
Value: 222,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{2, 2},
|
||||
Index: 2,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap2 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 222,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
SweepConfTarget: 111,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
sweepReq3 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{3, 3, 3},
|
||||
Value: 333,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{3, 3},
|
||||
Index: 3,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap3 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 333,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
SweepConfTarget: 111,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Feed the sweeps to the batcher.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// After inserting the primary (first) sweep, a spend monitor should be
|
||||
// registered.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
batcher.sweepReqs <- sweepReq2
|
||||
|
||||
batcher.sweepReqs <- sweepReq3
|
||||
|
||||
// Batcher should create a batch for the sweeps.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Find the batch and store it in a local variable for easier access.
|
||||
b := &batch{}
|
||||
for _, btch := range batcher.batches {
|
||||
if btch.primarySweepID == sweepReq1.SwapHash {
|
||||
b = btch
|
||||
}
|
||||
}
|
||||
|
||||
// Batcher should contain all sweeps.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(b.sweeps) == 3
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Verify that the batch has a primary sweep id that matches the first
|
||||
// inserted sweep, sweep1.
|
||||
require.Equal(t, b.primarySweepID, sweepReq1.SwapHash)
|
||||
|
||||
// Create the spending tx. In order to simulate an older version of the
|
||||
// batch transaction being confirmed, we only insert the primary sweep's
|
||||
// outpoint as a TxIn. This means that the other two sweeps did not
|
||||
// appear in the spending transaction. (This simulates a possible
|
||||
// scenario caused by RBF replacements.)
|
||||
spendingTx := &wire.MsgTx{
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: sweepReq1.Outpoint,
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: int64(sweepReq1.Value.ToUnit(btcutil.AmountSatoshi)),
|
||||
PkScript: []byte{3, 2, 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
spendingTxHash := spendingTx.TxHash()
|
||||
|
||||
spendDetail := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &sweepReq1.Outpoint,
|
||||
SpendingTx: spendingTx,
|
||||
SpenderTxHash: &spendingTxHash,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: 601,
|
||||
}
|
||||
|
||||
// Send the spending notification to the mock channel.
|
||||
lnd.SpendChannel <- spendDetail
|
||||
|
||||
// After receiving the spend notification the batch should progress to
|
||||
// the next step, which is monitoring for confirmations.
|
||||
<-lnd.RegisterConfChannel
|
||||
|
||||
// Eventually the batch reads the notification and proceeds to a closed
|
||||
// state.
|
||||
require.Eventually(t, func() bool {
|
||||
return b.state == Closed
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// While handling the spend notification the batch should detect that
|
||||
// some sweeps did not appear in the spending tx, therefore it redirects
|
||||
// them back to the batcher and the batcher inserts them in a new batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 2
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since second batch was created we check that it registered for its
|
||||
// primary sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// We mock the confirmation notification.
|
||||
lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
||||
Tx: spendingTx,
|
||||
}
|
||||
|
||||
// Eventually the batch receives the confirmation notification,
|
||||
// gracefully exits and the batcher deletes it.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Find the other batch, which includes the sweeps that did not appear
|
||||
// in the spending tx.
|
||||
b = &batch{}
|
||||
for _, btch := range batcher.batches {
|
||||
b = btch
|
||||
}
|
||||
|
||||
// After all the sweeps enter, it should contain 2 sweeps.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(b.sweeps) == 2
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// The batch should be in an open state.
|
||||
require.Equal(t, b.state, Open)
|
||||
}
|
||||
|
||||
// TestSweepBatcherNonWalletAddr tests that sweep requests that sweep to a non
|
||||
// wallet address enter individual batches.
|
||||
func TestSweepBatcherNonWalletAddr(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
batcherStore := NewStoreMock()
|
||||
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
|
||||
go func() {
|
||||
err := batcher.Run(ctx)
|
||||
if !strings.Contains(err.Error(), "context canceled") {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a sweep request.
|
||||
sweepReq1 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap1 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
},
|
||||
IsExternalAddr: true,
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err := store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Deliver sweep request to batcher.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Once batcher receives sweep request it will eventually spin up a
|
||||
// batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Insert the same swap twice, this should be a noop.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Create a second sweep request that has a timeout distance less than
|
||||
// our configured threshold.
|
||||
sweepReq2 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{2, 2, 2},
|
||||
Value: 222,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{2, 2},
|
||||
Index: 2,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap2 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance - 1,
|
||||
AmountRequested: 222,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
IsExternalAddr: true,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
batcher.sweepReqs <- sweepReq2
|
||||
|
||||
// Batcher should create a second batch as first batch is a non wallet
|
||||
// addr batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 2
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Create a third sweep request that has more timeout distance than
|
||||
// the default.
|
||||
sweepReq3 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{3, 3, 3},
|
||||
Value: 333,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{3, 3},
|
||||
Index: 3,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap3 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance + 1,
|
||||
AmountRequested: 333,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
IsExternalAddr: true,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
batcher.sweepReqs <- sweepReq3
|
||||
|
||||
// Batcher should create a new batch as timeout distance is greater than
|
||||
// the threshold
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 3
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
// Verify that each batch has the correct number of sweeps in it.
|
||||
for _, batch := range batcher.batches {
|
||||
switch batch.primarySweepID {
|
||||
case sweepReq1.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq2.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq3.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Check that all sweeps were stored.
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq1.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq2.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq3.SwapHash))
|
||||
}
|
||||
|
||||
// TestSweepBatcherComposite tests that sweep requests that sweep to both wallet
|
||||
// addresses and non-wallet addresses enter the correct batches.
|
||||
func TestSweepBatcherComposite(t *testing.T) {
|
||||
defer test.Guard(t)()
|
||||
|
||||
lnd := test.NewMockLnd()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := loopdb.NewStoreMock(t)
|
||||
|
||||
batcherStore := NewStoreMock()
|
||||
|
||||
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
|
||||
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
|
||||
go func() {
|
||||
err := batcher.Run(ctx)
|
||||
if !strings.Contains(err.Error(), "context canceled") {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a sweep request.
|
||||
sweepReq1 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{1, 1, 1},
|
||||
Value: 111,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{1, 1},
|
||||
Index: 1,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap1 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111,
|
||||
AmountRequested: 111,
|
||||
},
|
||||
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err := store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Create a second sweep request that has a timeout distance less than
|
||||
// our configured threshold.
|
||||
sweepReq2 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{2, 2, 2},
|
||||
Value: 222,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{2, 2},
|
||||
Index: 2,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap2 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance - 1,
|
||||
AmountRequested: 222,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Create a third sweep request that has less timeout distance than the
|
||||
// default max, but is not spending to a wallet address.
|
||||
sweepReq3 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{3, 3, 3},
|
||||
Value: 333,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{3, 3},
|
||||
Index: 3,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap3 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance - 3,
|
||||
AmountRequested: 333,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
IsExternalAddr: true,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Create a fourth sweep request that has a timeout which is not valid
|
||||
// for the first batch, so it will cause it to create a new batch.
|
||||
sweepReq4 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{4, 4, 4},
|
||||
Value: 444,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{4, 4},
|
||||
Index: 4,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap4 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance + 1,
|
||||
AmountRequested: 444,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq4.SwapHash, swap4)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Create a fifth sweep request that has a timeout which is not valid
|
||||
// for the first batch, but a valid timeout for the new batch.
|
||||
sweepReq5 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{5, 5, 5},
|
||||
Value: 555,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{5, 5},
|
||||
Index: 5,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap5 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance + 5,
|
||||
AmountRequested: 555,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq5.SwapHash, swap5)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Create a sixth sweep request that has a valid timeout for the new
|
||||
// batch, but is paying to a non-wallet address.
|
||||
sweepReq6 := SweepRequest{
|
||||
SwapHash: lntypes.Hash{6, 6, 6},
|
||||
Value: 666,
|
||||
Outpoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{6, 6},
|
||||
Index: 6,
|
||||
},
|
||||
Notifier: &dummyNotifier,
|
||||
}
|
||||
|
||||
swap6 := &loopdb.LoopOutContract{
|
||||
SwapContract: loopdb.SwapContract{
|
||||
CltvExpiry: 111 + defaultMaxTimeoutDistance + 6,
|
||||
AmountRequested: 666,
|
||||
},
|
||||
SwapInvoice: swapInvoice,
|
||||
IsExternalAddr: true,
|
||||
}
|
||||
|
||||
err = store.CreateLoopOut(ctx, sweepReq6.SwapHash, swap6)
|
||||
require.NoError(t, err)
|
||||
store.AssertLoopOutStored()
|
||||
|
||||
// Deliver sweep request to batcher.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
// Once batcher receives sweep request it will eventually spin up a
|
||||
// batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
// Insert the same swap twice, this should be a noop.
|
||||
batcher.sweepReqs <- sweepReq1
|
||||
|
||||
batcher.sweepReqs <- sweepReq2
|
||||
|
||||
// Batcher should not create a second batch as timeout distance is small
|
||||
// enough.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 1
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
batcher.sweepReqs <- sweepReq3
|
||||
|
||||
// Batcher should create a second batch as this sweep pays to a non
|
||||
// wallet address.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 2
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
batcher.sweepReqs <- sweepReq4
|
||||
|
||||
// Batcher should create a third batch as timeout distance is greater
|
||||
// than the threshold.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 3
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
batcher.sweepReqs <- sweepReq5
|
||||
|
||||
// Batcher should not create a fourth batch as timeout distance is small
|
||||
// enough for it to join the last batch.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 3
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
batcher.sweepReqs <- sweepReq6
|
||||
|
||||
// Batcher should create a fourth batch as this sweep pays to a non
|
||||
// wallet address.
|
||||
require.Eventually(t, func() bool {
|
||||
return len(batcher.batches) == 4
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Since a batch was created we check that it registered for its primary
|
||||
// sweep's spend.
|
||||
<-lnd.RegisterSpendChannel
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
// Verify that each batch has the correct number of sweeps in
|
||||
// it.
|
||||
for _, batch := range batcher.batches {
|
||||
switch batch.primarySweepID {
|
||||
case sweepReq1.SwapHash:
|
||||
if len(batch.sweeps) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq3.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq4.SwapHash:
|
||||
if len(batch.sweeps) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
case sweepReq6.SwapHash:
|
||||
if len(batch.sweeps) != 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, test.Timeout, eventuallyCheckFrequency)
|
||||
|
||||
// Check that all sweeps were stored.
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq1.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq2.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq3.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq4.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq5.SwapHash))
|
||||
require.True(t, batcherStore.AssertSweepStored(sweepReq6.SwapHash))
|
||||
}
|
||||
|
|
@ -73,31 +73,40 @@ func (c *mockChainNotifier) RegisterBlockEpochNtfn(ctx context.Context) (
|
|||
chan int32, chan error, error) {
|
||||
|
||||
blockErrorChan := make(chan error, 1)
|
||||
blockEpochChan := make(chan int32)
|
||||
blockEpochChan := make(chan int32, 1)
|
||||
|
||||
c.lnd.lock.Lock()
|
||||
c.lnd.blockHeightListeners = append(
|
||||
c.lnd.blockHeightListeners, blockEpochChan,
|
||||
)
|
||||
c.lnd.lock.Unlock()
|
||||
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
defer func() {
|
||||
c.lnd.lock.Lock()
|
||||
defer c.lnd.lock.Unlock()
|
||||
for i := 0; i < len(c.lnd.blockHeightListeners); i++ {
|
||||
if c.lnd.blockHeightListeners[i] == blockEpochChan {
|
||||
c.lnd.blockHeightListeners = append(
|
||||
c.lnd.blockHeightListeners[:i],
|
||||
c.lnd.blockHeightListeners[i+1:]...,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Send initial block height
|
||||
c.lnd.lock.Lock()
|
||||
select {
|
||||
case blockEpochChan <- c.lnd.Height:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
c.lnd.lock.Unlock()
|
||||
|
||||
for {
|
||||
select {
|
||||
case m := <-c.lnd.epochChannel:
|
||||
select {
|
||||
case blockEpochChan <- m:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
<-ctx.Done()
|
||||
}()
|
||||
|
||||
return blockEpochChan, blockErrorChan, nil
|
||||
|
|
|
|||
|
|
@ -259,3 +259,9 @@ func (ctx *Context) GetOutputIndex(tx *wire.MsgTx,
|
|||
func (ctx *Context) NotifyServerHeight(height int32) {
|
||||
require.NoError(ctx.T, ctx.Lnd.NotifyHeight(height))
|
||||
}
|
||||
|
||||
func (ctx *Context) AssertEpochListeners(numListeners int32) {
|
||||
require.Eventually(ctx.T, func() bool {
|
||||
return ctx.Lnd.EpochSubscribers() == numListeners
|
||||
}, Timeout, time.Millisecond*250)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
|
@ -63,13 +62,13 @@ func NewMockLnd() *LndMockServices {
|
|||
|
||||
SignOutputRawChannel: make(chan SignOutputRawRequest),
|
||||
|
||||
FailInvoiceChannel: make(chan lntypes.Hash, 2),
|
||||
epochChannel: make(chan int32),
|
||||
Height: testStartingHeight,
|
||||
NodePubkey: testNodePubkey,
|
||||
Signature: testSignature,
|
||||
SignatureMsg: testSignatureMsg,
|
||||
Invoices: make(map[lntypes.Hash]*lndclient.Invoice),
|
||||
FailInvoiceChannel: make(chan lntypes.Hash, 2),
|
||||
blockHeightListeners: make([]chan int32, 0),
|
||||
Height: testStartingHeight,
|
||||
NodePubkey: testNodePubkey,
|
||||
Signature: testSignature,
|
||||
SignatureMsg: testSignatureMsg,
|
||||
Invoices: make(map[lntypes.Hash]*lndclient.Invoice),
|
||||
}
|
||||
|
||||
lightningClient.lnd = &lnd
|
||||
|
|
@ -139,7 +138,7 @@ type LndMockServices struct {
|
|||
SendOutputsChannel chan wire.MsgTx
|
||||
SettleInvoiceChannel chan lntypes.Preimage
|
||||
FailInvoiceChannel chan lntypes.Hash
|
||||
epochChannel chan int32
|
||||
blockHeightListeners []chan int32
|
||||
|
||||
ConfChannel chan *chainntnfs.TxConfirmation
|
||||
RegisterConfChannel chan *ConfRegistration
|
||||
|
|
@ -177,15 +176,28 @@ type LndMockServices struct {
|
|||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// EpochSubscribers returns the number of subscribers to block epoch
|
||||
// notifications.
|
||||
func (s *LndMockServices) EpochSubscribers() int32 {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return int32(len(s.blockHeightListeners))
|
||||
}
|
||||
|
||||
// NotifyHeight notifies a new block height.
|
||||
func (s *LndMockServices) NotifyHeight(height int32) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.Height = height
|
||||
|
||||
select {
|
||||
case s.epochChannel <- height:
|
||||
case <-time.After(Timeout):
|
||||
return ErrTimeout
|
||||
for _, listener := range s.blockHeightListeners {
|
||||
lis := listener
|
||||
go func() {
|
||||
lis <- height
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/sweep"
|
||||
"github.com/lightninglabs/loop/sweepbatcher"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
|
|
@ -36,7 +37,7 @@ type testContext struct {
|
|||
serverMock *serverMock
|
||||
swapClient *Client
|
||||
statusChan chan SwapInfo
|
||||
store *storeMock
|
||||
store *loopdb.StoreMock
|
||||
expiryChan chan time.Time
|
||||
runErr chan error
|
||||
stop func()
|
||||
|
|
@ -51,6 +52,24 @@ func mockVerifySchnorrSigFail(pubKey *btcec.PublicKey, hash,
|
|||
return fmt.Errorf("invalid sig")
|
||||
}
|
||||
|
||||
// mockVerifySchnorrSigSuccess is used to simulate successful taproot keyspend
|
||||
// signature verification. If passed to the executeConfig we'll test an
|
||||
// uncooperative server and will fall back to scriptspend sweep.
|
||||
func mockVerifySchnorrSigSuccess(pubKey *btcec.PublicKey, hash,
|
||||
sig []byte) error {
|
||||
|
||||
return fmt.Errorf("invalid sig")
|
||||
}
|
||||
|
||||
func mockMuSig2SignSweep(ctx context.Context,
|
||||
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
|
||||
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
|
||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func newSwapClient(config *clientConfig) *Client {
|
||||
sweeper := &sweep.Sweeper{
|
||||
Lnd: config.LndServices,
|
||||
|
|
@ -58,10 +77,20 @@ func newSwapClient(config *clientConfig) *Client {
|
|||
|
||||
lndServices := config.LndServices
|
||||
|
||||
batcherStore := sweepbatcher.NewStoreMock()
|
||||
|
||||
batcher := sweepbatcher.NewBatcher(
|
||||
config.LndServices.WalletKit, config.LndServices.ChainNotifier,
|
||||
config.LndServices.Signer, mockMuSig2SignSweep,
|
||||
mockVerifySchnorrSigSuccess, config.LndServices.ChainParams,
|
||||
batcherStore, config.Store,
|
||||
)
|
||||
|
||||
executor := newExecutor(&executorConfig{
|
||||
lnd: lndServices,
|
||||
store: config.Store,
|
||||
sweeper: sweeper,
|
||||
batcher: batcher,
|
||||
createExpiryTimer: config.CreateExpiryTimer,
|
||||
cancelSwap: config.Server.CancelLoopOutSwap,
|
||||
verifySchnorrSig: mockVerifySchnorrSigFail,
|
||||
|
|
@ -83,15 +112,15 @@ func createClientTestContext(t *testing.T,
|
|||
clientLnd := test.NewMockLnd()
|
||||
serverMock := newServerMock(clientLnd)
|
||||
|
||||
store := newStoreMock(t)
|
||||
store := loopdb.NewStoreMock(t)
|
||||
for _, s := range pendingSwaps {
|
||||
store.loopOutSwaps[s.Hash] = s.Contract
|
||||
store.LoopOutSwaps[s.Hash] = s.Contract
|
||||
|
||||
updates := []loopdb.SwapStateData{}
|
||||
for _, e := range s.Events {
|
||||
updates = append(updates, e.SwapStateData)
|
||||
}
|
||||
store.loopOutUpdates[s.Hash] = updates
|
||||
store.LoopOutUpdates[s.Hash] = updates
|
||||
}
|
||||
|
||||
expiryChan := make(chan time.Time)
|
||||
|
|
@ -147,7 +176,7 @@ func (ctx *testContext) finish() {
|
|||
}
|
||||
func (ctx *testContext) assertIsDone() {
|
||||
require.NoError(ctx.Context.T, ctx.Context.Lnd.IsDone())
|
||||
require.NoError(ctx.Context.T, ctx.store.isDone())
|
||||
require.NoError(ctx.Context.T, ctx.store.IsDone())
|
||||
|
||||
select {
|
||||
case <-ctx.statusChan:
|
||||
|
|
@ -159,19 +188,19 @@ func (ctx *testContext) assertIsDone() {
|
|||
func (ctx *testContext) assertStored() {
|
||||
ctx.Context.T.Helper()
|
||||
|
||||
ctx.store.assertLoopOutStored()
|
||||
ctx.store.AssertLoopOutStored()
|
||||
}
|
||||
|
||||
func (ctx *testContext) assertStorePreimageReveal() {
|
||||
ctx.Context.T.Helper()
|
||||
|
||||
ctx.store.assertStorePreimageReveal()
|
||||
ctx.store.AssertStorePreimageReveal()
|
||||
}
|
||||
|
||||
func (ctx *testContext) assertStoreFinished(expectedResult loopdb.SwapState) {
|
||||
ctx.Context.T.Helper()
|
||||
|
||||
ctx.store.assertStoreFinished(expectedResult)
|
||||
ctx.store.AssertStoreFinished(expectedResult)
|
||||
}
|
||||
|
||||
func (ctx *testContext) assertStatus(expectedState loopdb.SwapState) {
|
||||
|
|
@ -250,3 +279,11 @@ func (ctx *testContext) assertPreimagePush(preimage lntypes.Preimage) {
|
|||
ctx.Context.T.Fatalf("preimage not pushed")
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *testContext) AssertEpochListeners(numListeners int32) {
|
||||
ctx.Context.T.Helper()
|
||||
|
||||
require.Eventually(ctx.Context.T, func() bool {
|
||||
return ctx.Lnd.EpochSubscribers() == numListeners
|
||||
}, test.Timeout, time.Millisecond*250)
|
||||
}
|
||||
|
|
|
|||
77
utils/htlc_utils.go
Normal file
77
utils/htlc_utils.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
)
|
||||
|
||||
// GetHtlc composes and returns the on-chain swap script.
|
||||
func GetHtlc(hash lntypes.Hash, contract *loopdb.SwapContract,
|
||||
chainParams *chaincfg.Params) (*swap.Htlc, error) {
|
||||
|
||||
switch GetHtlcScriptVersion(contract.ProtocolVersion) {
|
||||
case swap.HtlcV2:
|
||||
return swap.NewHtlcV2(
|
||||
contract.CltvExpiry, contract.HtlcKeys.SenderScriptKey,
|
||||
contract.HtlcKeys.ReceiverScriptKey, hash,
|
||||
chainParams,
|
||||
)
|
||||
|
||||
case swap.HtlcV3:
|
||||
// Swaps that implement the new MuSig2 protocol will be expected
|
||||
// to use the 1.0RC2 MuSig2 key derivation scheme.
|
||||
muSig2Version := input.MuSig2Version040
|
||||
if contract.ProtocolVersion >= loopdb.ProtocolVersionMuSig2 {
|
||||
muSig2Version = input.MuSig2Version100RC2
|
||||
}
|
||||
|
||||
return swap.NewHtlcV3(
|
||||
muSig2Version,
|
||||
contract.CltvExpiry,
|
||||
contract.HtlcKeys.SenderInternalPubKey,
|
||||
contract.HtlcKeys.ReceiverInternalPubKey,
|
||||
contract.HtlcKeys.SenderScriptKey,
|
||||
contract.HtlcKeys.ReceiverScriptKey,
|
||||
hash, chainParams,
|
||||
)
|
||||
}
|
||||
|
||||
return nil, swap.ErrInvalidScriptVersion
|
||||
}
|
||||
|
||||
// GetHtlcScriptVersion returns the correct HTLC script version for the passed
|
||||
// protocol version.
|
||||
func GetHtlcScriptVersion(
|
||||
protocolVersion loopdb.ProtocolVersion) swap.ScriptVersion {
|
||||
|
||||
// If the swap was initiated before we had our v3 script, use v2.
|
||||
if protocolVersion < loopdb.ProtocolVersionHtlcV3 ||
|
||||
protocolVersion == loopdb.ProtocolVersionUnrecorded {
|
||||
|
||||
return swap.HtlcV2
|
||||
}
|
||||
|
||||
return swap.HtlcV3
|
||||
}
|
||||
|
||||
// ObtainSwapPaymentAddr will retrieve the payment addr from the passed invoice.
|
||||
func ObtainSwapPaymentAddr(swapInvoice string, chainParams *chaincfg.Params) (
|
||||
*[32]byte, error) {
|
||||
|
||||
swapPayReq, err := zpay32.Decode(swapInvoice, chainParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if swapPayReq.PaymentAddr == nil {
|
||||
return nil, fmt.Errorf("expected payment address for invoice")
|
||||
}
|
||||
|
||||
return swapPayReq.PaymentAddr, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue