mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticaddr: expose tracked deposit availability
Build list and summary responses from tracked deposit records instead of raw wallet UTXOs so RPC clients see the manager availability state. Split unconfirmed value from confirmed deposited value in summaries, and reject manual loop-in quotes for selected deposits that are not currently Deposited.
This commit is contained in:
parent
e4bcc94a36
commit
e8dd3aa009
4 changed files with 366 additions and 95 deletions
|
|
@ -96,7 +96,7 @@ type swapClientServer struct {
|
|||
reservationManager *reservation.Manager
|
||||
instantOutManager *instantout.Manager
|
||||
staticAddressManager *address.Manager
|
||||
depositManager *deposit.Manager
|
||||
depositManager staticAddressDepositManager
|
||||
withdrawalManager *withdraw.Manager
|
||||
staticLoopInManager *loopin.Manager
|
||||
openChannelManager *openchannel.Manager
|
||||
|
|
@ -112,6 +112,31 @@ type swapClientServer struct {
|
|||
stopDaemon func()
|
||||
}
|
||||
|
||||
// staticAddressDepositManager is the deposit manager behavior required by the
|
||||
// RPC server.
|
||||
type staticAddressDepositManager interface {
|
||||
// EnsureDepositsFresh reconciles tracked deposits with lnd's current
|
||||
// wallet view before user-facing deposit selection.
|
||||
EnsureDepositsFresh(context.Context) error
|
||||
|
||||
// GetActiveDepositsInState returns active deposits that are currently in
|
||||
// the requested state.
|
||||
GetActiveDepositsInState(fsm.StateType) ([]*deposit.Deposit, error)
|
||||
|
||||
// DepositsForOutpoints returns known deposit records for the requested
|
||||
// outpoints, optionally skipping unknown outpoints.
|
||||
DepositsForOutpoints(context.Context, []string, bool) (
|
||||
[]*deposit.Deposit, error)
|
||||
|
||||
// GetVisibleDeposits returns deposits that should be shown in normal
|
||||
// user-facing views.
|
||||
GetVisibleDeposits(context.Context) ([]*deposit.Deposit, error)
|
||||
|
||||
// GetAllDeposits returns all known deposit records, including historical
|
||||
// records that are no longer user-visible.
|
||||
GetAllDeposits(context.Context) ([]*deposit.Deposit, error)
|
||||
}
|
||||
|
||||
// LoopOut initiates a loop out swap with the given parameters. The call returns
|
||||
// after the swap has been set up with the swap server. From that point onwards,
|
||||
// progress can be tracked via the LoopOutStatus stream that is returned from
|
||||
|
|
@ -977,15 +1002,22 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
|
|||
return nil, fmt.Errorf("expected %d deposits, got %d",
|
||||
len(req.DepositOutpoints),
|
||||
len(depositList.FilteredDeposits))
|
||||
} else {
|
||||
numDeposits = len(depositList.FilteredDeposits)
|
||||
}
|
||||
numDeposits = len(depositList.FilteredDeposits)
|
||||
|
||||
// In case we quote for deposits, we send the server both the
|
||||
// selected value and the number of deposits. This is so the
|
||||
// server can probe the selected value and calculate the per
|
||||
// input fee.
|
||||
for _, deposit := range depositList.FilteredDeposits {
|
||||
// For a manual quote we require the current state to be
|
||||
// Deposited so a stale client-side outpoint selection
|
||||
// fails early instead of making it to swap initiation.
|
||||
if deposit.State != looprpc.DepositState_DEPOSITED {
|
||||
return nil, fmt.Errorf("deposit %s is not "+
|
||||
"currently available", deposit.Outpoint)
|
||||
}
|
||||
|
||||
totalDepositAmount += btcutil.Amount(
|
||||
deposit.Value,
|
||||
)
|
||||
|
|
@ -1693,58 +1725,40 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
|
|||
}
|
||||
|
||||
// ListUnspentRaw returns the unspent wallet view of the backing lnd
|
||||
// wallet. It might be that deposits show up there that are actually
|
||||
// not spendable because they already have been used but not yet spent
|
||||
// by the server. We filter out such deposits here.
|
||||
// wallet. Static loop-in initiation requires an active deposit record,
|
||||
// so only deposits that are both wallet-visible and tracked as
|
||||
// Deposited are returned here.
|
||||
var (
|
||||
outpoints []string
|
||||
isUnspent = make(map[wire.OutPoint]struct{})
|
||||
)
|
||||
|
||||
// Keep track of confirmed outpoints that we need to check against our
|
||||
// database.
|
||||
confirmedToCheck := make(map[wire.OutPoint]struct{})
|
||||
|
||||
for _, utxo := range utxos {
|
||||
if utxo.Confirmations < deposit.MinConfs {
|
||||
// Unconfirmed deposits are always available.
|
||||
isUnspent[utxo.OutPoint] = struct{}{}
|
||||
} else {
|
||||
// Confirmed deposits need to be checked.
|
||||
outpoints = append(outpoints, utxo.OutPoint.String())
|
||||
confirmedToCheck[utxo.OutPoint] = struct{}{}
|
||||
}
|
||||
outpoints = append(outpoints, utxo.OutPoint.String())
|
||||
}
|
||||
|
||||
err = s.depositManager.EnsureDepositsFresh(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check the spent status of the deposits by looking at their states.
|
||||
ignoreUnknownOutpoints := false
|
||||
ignoreUnknownOutpoints := true
|
||||
deposits, err := s.depositManager.DepositsForOutpoints(
|
||||
ctx, outpoints, ignoreUnknownOutpoints,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range deposits {
|
||||
// A nil deposit means we don't have a record for it. We'll
|
||||
// handle this case after the loop.
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the deposit is in the "Deposited" state, it's available.
|
||||
if d.IsInState(deposit.Deposited) {
|
||||
isUnspent[d.OutPoint] = struct{}{}
|
||||
}
|
||||
|
||||
// We have a record for this deposit, so we no longer need to
|
||||
// check it.
|
||||
delete(confirmedToCheck, d.OutPoint)
|
||||
}
|
||||
|
||||
// Any remaining outpoints in confirmedToCheck are ones that lnd knows
|
||||
// about but we don't. These are new, unspent deposits.
|
||||
for op := range confirmedToCheck {
|
||||
isUnspent[op] = struct{}{}
|
||||
}
|
||||
|
||||
// Prepare the list of unspent deposits for the rpc response.
|
||||
|
|
@ -1988,9 +2002,10 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
|
|||
for _, d := range ds {
|
||||
state := toClientDepositState(d.GetState())
|
||||
confirmationHeight := d.GetConfirmationHeight()
|
||||
blocksUntilExpiry := confirmationHeight +
|
||||
int64(addrParams.Expiry) -
|
||||
int64(lndInfo.BlockHeight)
|
||||
blocksUntilExpiry := depositBlocksUntilExpiry(
|
||||
confirmationHeight, addrParams.Expiry,
|
||||
int64(lndInfo.BlockHeight),
|
||||
)
|
||||
|
||||
pd := &looprpc.Deposit{
|
||||
Id: d.ID[:],
|
||||
|
|
@ -2082,23 +2097,16 @@ func (s *swapClientServer) GetStaticAddressSummary(ctx context.Context,
|
|||
htlcTimeoutSwept int64
|
||||
)
|
||||
|
||||
// Value unconfirmed.
|
||||
utxos, err := s.staticAddressManager.ListUnspent(
|
||||
ctx, 0, deposit.MinConfs-1,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, u := range utxos {
|
||||
valueUnconfirmed += int64(u.Value)
|
||||
}
|
||||
|
||||
// Confirmed total values by category.
|
||||
// Total values by category.
|
||||
for _, d := range allDeposits {
|
||||
value := int64(d.Value)
|
||||
switch d.GetState() {
|
||||
case deposit.Deposited:
|
||||
valueDeposited += value
|
||||
if d.GetConfirmationHeight() <= 0 {
|
||||
valueUnconfirmed += value
|
||||
} else {
|
||||
valueDeposited += value
|
||||
}
|
||||
|
||||
case deposit.Expired:
|
||||
valueExpired += value
|
||||
|
|
@ -2248,13 +2256,27 @@ func (s *swapClientServer) populateBlocksUntilExpiry(ctx context.Context,
|
|||
return err
|
||||
}
|
||||
for i := range len(deposits) {
|
||||
deposits[i].BlocksUntilExpiry =
|
||||
deposits[i].ConfirmationHeight +
|
||||
int64(params.Expiry) - bestBlockHeight
|
||||
deposits[i].BlocksUntilExpiry = depositBlocksUntilExpiry(
|
||||
deposits[i].ConfirmationHeight, params.Expiry,
|
||||
bestBlockHeight,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// depositBlocksUntilExpiry returns the remaining blocks until a deposit
|
||||
// expires. Unconfirmed deposits return the full CSV value because the timeout
|
||||
// has not started yet.
|
||||
func depositBlocksUntilExpiry(confirmationHeight int64, expiry uint32,
|
||||
bestBlockHeight int64) int64 {
|
||||
|
||||
if confirmationHeight <= 0 {
|
||||
return int64(expiry)
|
||||
}
|
||||
|
||||
return confirmationHeight + int64(expiry) - bestBlockHeight
|
||||
}
|
||||
|
||||
// StaticOpenChannel initiates an open channel request using static address
|
||||
// deposits.
|
||||
func (s *swapClientServer) StaticOpenChannel(ctx context.Context,
|
||||
|
|
|
|||
23
loopd/swapclient_server_deposit_test.go
Normal file
23
loopd/swapclient_server_deposit_test.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package loopd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDepositBlocksUntilExpiry checks blocks-until-expiry handling for
|
||||
// confirmed and unconfirmed deposits.
|
||||
func TestDepositBlocksUntilExpiry(t *testing.T) {
|
||||
t.Run("unconfirmed", func(t *testing.T) {
|
||||
if blocks := depositBlocksUntilExpiry(0, 144, 500); blocks != 144 {
|
||||
t.Fatalf("expected 144 blocks for unconfirmed deposit, got %d",
|
||||
blocks)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("confirmed", func(t *testing.T) {
|
||||
if blocks := depositBlocksUntilExpiry(450, 144, 500); blocks != 94 {
|
||||
t.Fatalf("expected 94 blocks until expiry, got %d",
|
||||
blocks)
|
||||
}
|
||||
})
|
||||
}
|
||||
135
loopd/swapclient_server_staticaddr_test.go
Normal file
135
loopd/swapclient_server_staticaddr_test.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package loopd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btclog/v2"
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
mock_lnd "github.com/lightninglabs/loop/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type staticAddrDepositStore struct {
|
||||
allDeposits []*deposit.Deposit
|
||||
byOutpoint map[string]*deposit.Deposit
|
||||
}
|
||||
|
||||
// CreateDeposit implements deposit.Store for static address server tests.
|
||||
func (s *staticAddrDepositStore) CreateDeposit(context.Context,
|
||||
*deposit.Deposit) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDeposit implements deposit.Store for static address server tests.
|
||||
func (s *staticAddrDepositStore) UpdateDeposit(context.Context,
|
||||
*deposit.Deposit) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeposit implements deposit.Store for static address server tests.
|
||||
func (s *staticAddrDepositStore) GetDeposit(context.Context,
|
||||
deposit.ID) (*deposit.Deposit, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DepositForOutpoint returns the deposit for the requested outpoint.
|
||||
func (s *staticAddrDepositStore) DepositForOutpoint(_ context.Context,
|
||||
outpoint string) (*deposit.Deposit, error) {
|
||||
|
||||
if deposit, ok := s.byOutpoint[outpoint]; ok {
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
return nil, deposit.ErrDepositNotFound
|
||||
}
|
||||
|
||||
// AllDeposits returns all deposits seeded into the test store.
|
||||
func (s *staticAddrDepositStore) AllDeposits(context.Context) (
|
||||
[]*deposit.Deposit, error) {
|
||||
|
||||
return s.allDeposits, nil
|
||||
}
|
||||
|
||||
// newTestDepositManager creates a deposit manager backed by seeded deposits.
|
||||
func newTestDepositManager(
|
||||
deposits ...*deposit.Deposit) *deposit.Manager {
|
||||
|
||||
byOutpoint := make(map[string]*deposit.Deposit, len(deposits))
|
||||
for _, deposit := range deposits {
|
||||
byOutpoint[deposit.OutPoint.String()] = deposit
|
||||
}
|
||||
|
||||
return deposit.NewManager(&deposit.ManagerConfig{
|
||||
Store: &staticAddrDepositStore{
|
||||
allDeposits: deposits,
|
||||
byOutpoint: byOutpoint,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// newTestStaticAddressContext creates static address test dependencies.
|
||||
func newTestStaticAddressContext(t *testing.T) (*address.Manager,
|
||||
*mock_lnd.LndMockServices) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
mock := mock_lnd.NewMockLnd()
|
||||
_, client := mock_lnd.CreateKey(1)
|
||||
_, server := mock_lnd.CreateKey(2)
|
||||
|
||||
addrStore := &mockAddressStore{
|
||||
params: []*script.Parameters{{
|
||||
ClientPubkey: client,
|
||||
ServerPubkey: server,
|
||||
Expiry: 10,
|
||||
PkScript: []byte("pkscript"),
|
||||
}},
|
||||
}
|
||||
|
||||
addrMgr, err := address.NewManager(&address.ManagerConfig{
|
||||
Store: addrStore,
|
||||
WalletKit: mock.WalletKit,
|
||||
ChainParams: mock.ChainParams,
|
||||
}, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
return addrMgr, mock
|
||||
}
|
||||
|
||||
// TestGetLoopInQuoteRejectsUnavailableSelectedDeposit verifies manual quote
|
||||
// requests fail for selected deposits that are no longer available.
|
||||
func TestGetLoopInQuoteRejectsUnavailableSelectedDeposit(t *testing.T) {
|
||||
t.Parallel()
|
||||
setLogger(btclog.Disabled)
|
||||
|
||||
locked := &deposit.Deposit{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{6},
|
||||
Index: 6,
|
||||
},
|
||||
Value: btcutil.Amount(5_000),
|
||||
}
|
||||
locked.SetState(deposit.LoopingIn)
|
||||
|
||||
addrMgr, lnd := newTestStaticAddressContext(t)
|
||||
server := &swapClientServer{
|
||||
depositManager: newTestDepositManager(locked),
|
||||
staticAddressManager: addrMgr,
|
||||
lnd: &lnd.LndServices,
|
||||
}
|
||||
|
||||
_, err := server.GetLoopInQuote(context.Background(), &looprpc.QuoteRequest{
|
||||
DepositOutpoints: []string{locked.OutPoint.String()},
|
||||
})
|
||||
require.ErrorContains(t, err, "is not currently available")
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package loopd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -1321,7 +1322,7 @@ func (s *mockDepositStore) DepositForOutpoint(_ context.Context,
|
|||
if d, ok := s.byOutpoint[outpoint]; ok {
|
||||
return d, nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, deposit.ErrDepositNotFound
|
||||
}
|
||||
|
||||
func (s *mockDepositStore) AllDeposits(_ context.Context) ([]*deposit.Deposit,
|
||||
|
|
@ -1335,6 +1336,90 @@ func (s *mockDepositStore) AllDeposits(_ context.Context) ([]*deposit.Deposit,
|
|||
return deposits, nil
|
||||
}
|
||||
|
||||
// listUnspentDepositManager backs ListUnspentDeposits tests without requiring
|
||||
// the full deposit manager event loop.
|
||||
type listUnspentDepositManager struct {
|
||||
byOutpoint map[string]*deposit.Deposit
|
||||
|
||||
ensureDepositsFreshCalls int
|
||||
onEnsureDepositsFresh func(*listUnspentDepositManager)
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) EnsureDepositsFresh(
|
||||
context.Context) error {
|
||||
|
||||
m.ensureDepositsFreshCalls++
|
||||
if m.onEnsureDepositsFresh != nil {
|
||||
m.onEnsureDepositsFresh(m)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) GetActiveDepositsInState(
|
||||
state fsm.StateType) ([]*deposit.Deposit, error) {
|
||||
|
||||
deposits := make([]*deposit.Deposit, 0, len(m.byOutpoint))
|
||||
for _, d := range m.byOutpoint {
|
||||
if !d.IsInState(state) {
|
||||
continue
|
||||
}
|
||||
|
||||
deposits = append(deposits, d)
|
||||
}
|
||||
|
||||
return deposits, nil
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) DepositsForOutpoints(
|
||||
_ context.Context, outpoints []string, ignoreUnknown bool) (
|
||||
[]*deposit.Deposit, error) {
|
||||
|
||||
deposits := make([]*deposit.Deposit, 0, len(outpoints))
|
||||
seen := make(map[string]struct{}, len(outpoints))
|
||||
for i, outpoint := range outpoints {
|
||||
if _, ok := seen[outpoint]; ok {
|
||||
return nil, fmt.Errorf("duplicate outpoint %s "+
|
||||
"at index %d", outpoint, i)
|
||||
}
|
||||
seen[outpoint] = struct{}{}
|
||||
|
||||
d, ok := m.byOutpoint[outpoint]
|
||||
if !ok {
|
||||
if ignoreUnknown {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, deposit.ErrDepositNotFound
|
||||
}
|
||||
|
||||
deposits = append(deposits, d)
|
||||
}
|
||||
|
||||
return deposits, nil
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) GetVisibleDeposits(
|
||||
context.Context) ([]*deposit.Deposit, error) {
|
||||
|
||||
return m.allDeposits(), nil
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) GetAllDeposits(
|
||||
context.Context) ([]*deposit.Deposit, error) {
|
||||
|
||||
return m.allDeposits(), nil
|
||||
}
|
||||
|
||||
func (m *listUnspentDepositManager) allDeposits() []*deposit.Deposit {
|
||||
deposits := make([]*deposit.Deposit, 0, len(m.byOutpoint))
|
||||
for _, d := range m.byOutpoint {
|
||||
deposits = append(deposits, d)
|
||||
}
|
||||
|
||||
return deposits
|
||||
}
|
||||
|
||||
// TestListUnspentDeposits tests filtering behavior of ListUnspentDeposits.
|
||||
func TestListUnspentDeposits(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
|
@ -1376,39 +1461,41 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
minConfs := int64(deposit.MinConfs)
|
||||
utxoBelow := makeUtxo(0, minConfs-1) // always included
|
||||
utxoAt := makeUtxo(1, minConfs) // included only if Deposited
|
||||
utxoAbove1 := makeUtxo(2, minConfs+1)
|
||||
utxoAbove2 := makeUtxo(3, minConfs+2)
|
||||
utxoUnknown := makeUtxo(0, 0)
|
||||
utxoDeposited := makeUtxo(1, 1)
|
||||
utxoWithdrawn := makeUtxo(2, 2)
|
||||
utxoLoopingIn := makeUtxo(3, 5)
|
||||
utxoConfirmedUnknown := makeUtxo(4, 3)
|
||||
|
||||
// Helper to build the deposit manager with specific states.
|
||||
buildDepositMgr := func(
|
||||
states map[wire.OutPoint]fsm.StateType) *deposit.Manager {
|
||||
states map[wire.OutPoint]fsm.StateType) *listUnspentDepositManager {
|
||||
|
||||
store := &mockDepositStore{
|
||||
depMgr := &listUnspentDepositManager{
|
||||
byOutpoint: make(map[string]*deposit.Deposit),
|
||||
}
|
||||
for op, state := range states {
|
||||
d := &deposit.Deposit{OutPoint: op}
|
||||
d.SetState(state)
|
||||
store.byOutpoint[op.String()] = d
|
||||
depMgr.byOutpoint[op.String()] = d
|
||||
}
|
||||
|
||||
return deposit.NewManager(&deposit.ManagerConfig{Store: store})
|
||||
return depMgr
|
||||
}
|
||||
|
||||
// Include below-min-conf and >=min with Deposited; exclude others.
|
||||
t.Run("below min conf always, Deposited included, others excluded",
|
||||
// Only known Deposited records are available. Unknown deposits and
|
||||
// known non-Deposited states are excluded.
|
||||
t.Run("only known Deposited included",
|
||||
func(t *testing.T) {
|
||||
mock.SetListUnspent([]*lnwallet.Utxo{
|
||||
utxoBelow, utxoAt, utxoAbove1, utxoAbove2,
|
||||
utxoUnknown, utxoDeposited, utxoWithdrawn,
|
||||
utxoLoopingIn,
|
||||
})
|
||||
|
||||
depMgr := buildDepositMgr(map[wire.OutPoint]fsm.StateType{
|
||||
utxoAt.OutPoint: deposit.Deposited,
|
||||
utxoAbove1.OutPoint: deposit.Withdrawn,
|
||||
utxoAbove2.OutPoint: deposit.LoopingIn,
|
||||
utxoDeposited.OutPoint: deposit.Deposited,
|
||||
utxoWithdrawn.OutPoint: deposit.Withdrawn,
|
||||
utxoLoopingIn.OutPoint: deposit.LoopingIn,
|
||||
})
|
||||
|
||||
server := &swapClientServer{
|
||||
|
|
@ -1420,9 +1507,10 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
ctx, &looprpc.ListUnspentDepositsRequest{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, depMgr.ensureDepositsFreshCalls)
|
||||
|
||||
// Expect utxoBelow and utxoAt only.
|
||||
require.Len(t, resp.Utxos, 2)
|
||||
// Expect the Deposited utxo only.
|
||||
require.Len(t, resp.Utxos, 1)
|
||||
got := map[string]struct{}{}
|
||||
for _, u := range resp.Utxos {
|
||||
got[u.Outpoint] = struct{}{}
|
||||
|
|
@ -1430,25 +1518,23 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
// same across utxos.
|
||||
require.NotEmpty(t, u.StaticAddress)
|
||||
}
|
||||
_, ok1 := got[utxoBelow.OutPoint.String()]
|
||||
_, ok2 := got[utxoAt.OutPoint.String()]
|
||||
require.True(t, ok1)
|
||||
require.True(t, ok2)
|
||||
_, ok := got[utxoDeposited.OutPoint.String()]
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
// Swap states, now include utxoBelow and utxoAbove1.
|
||||
t.Run("Deposited on >=min included; non-Deposited excluded",
|
||||
// Confirmation depth no longer changes availability; state does.
|
||||
t.Run("availability ignores conf depth once deposit state is known",
|
||||
func(t *testing.T) {
|
||||
mock.SetListUnspent(
|
||||
[]*lnwallet.Utxo{
|
||||
utxoBelow, utxoAt, utxoAbove1,
|
||||
utxoAbove2,
|
||||
utxoUnknown, utxoDeposited,
|
||||
utxoWithdrawn, utxoLoopingIn,
|
||||
})
|
||||
|
||||
depMgr := buildDepositMgr(map[wire.OutPoint]fsm.StateType{
|
||||
utxoAt.OutPoint: deposit.Withdrawn,
|
||||
utxoAbove1.OutPoint: deposit.Deposited,
|
||||
utxoAbove2.OutPoint: deposit.Withdrawn,
|
||||
utxoDeposited.OutPoint: deposit.Deposited,
|
||||
utxoWithdrawn.OutPoint: deposit.Withdrawn,
|
||||
utxoLoopingIn.OutPoint: deposit.LoopingIn,
|
||||
})
|
||||
|
||||
server := &swapClientServer{
|
||||
|
|
@ -1460,26 +1546,32 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
ctx, &looprpc.ListUnspentDepositsRequest{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, depMgr.ensureDepositsFreshCalls)
|
||||
|
||||
require.Len(t, resp.Utxos, 2)
|
||||
require.Len(t, resp.Utxos, 1)
|
||||
got := map[string]struct{}{}
|
||||
for _, u := range resp.Utxos {
|
||||
got[u.Outpoint] = struct{}{}
|
||||
}
|
||||
_, ok1 := got[utxoBelow.OutPoint.String()]
|
||||
_, ok2 := got[utxoAbove1.OutPoint.String()]
|
||||
require.True(t, ok1)
|
||||
require.True(t, ok2)
|
||||
_, ok := got[utxoDeposited.OutPoint.String()]
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
// Confirmed UTXO not present in store should be included.
|
||||
t.Run("confirmed utxo not in store is included", func(t *testing.T) {
|
||||
// Only return a confirmed UTXO from lnd and make sure the
|
||||
// deposit manager/store doesn't know about it.
|
||||
mock.SetListUnspent([]*lnwallet.Utxo{utxoAbove2})
|
||||
// A wallet-visible UTXO reconciled by EnsureDepositsFresh should be
|
||||
// returned in the same ListUnspentDeposits call.
|
||||
t.Run("freshly reconciled wallet utxo is included", func(t *testing.T) {
|
||||
mock.SetListUnspent([]*lnwallet.Utxo{utxoConfirmedUnknown})
|
||||
|
||||
// Empty store (no states for any outpoint).
|
||||
depMgr := buildDepositMgr(map[wire.OutPoint]fsm.StateType{})
|
||||
depMgr.onEnsureDepositsFresh = func(
|
||||
m *listUnspentDepositManager) {
|
||||
|
||||
d := &deposit.Deposit{
|
||||
OutPoint: utxoConfirmedUnknown.OutPoint,
|
||||
}
|
||||
d.SetState(deposit.Deposited)
|
||||
m.byOutpoint[d.OutPoint.String()] = d
|
||||
}
|
||||
|
||||
server := &swapClientServer{
|
||||
staticAddressManager: addrMgr,
|
||||
|
|
@ -1490,13 +1582,12 @@ func TestListUnspentDeposits(t *testing.T) {
|
|||
ctx, &looprpc.ListUnspentDepositsRequest{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, depMgr.ensureDepositsFreshCalls)
|
||||
|
||||
// We expect the confirmed UTXO to be included even though it
|
||||
// doesn't exist in the store yet.
|
||||
require.Len(t, resp.Utxos, 1)
|
||||
require.Equal(
|
||||
t, utxoAbove2.OutPoint.String(), resp.Utxos[0].Outpoint,
|
||||
t, utxoConfirmedUnknown.OutPoint.String(),
|
||||
resp.Utxos[0].Outpoint,
|
||||
)
|
||||
require.NotEmpty(t, resp.Utxos[0].StaticAddress)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue