mirror of https://github.com/lightninglabs/loop
unit: manager tests
parent
4ce3e4baaf
commit
3c03fc79f7
@ -0,0 +1,147 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultServerPubkeyBytes, _ = hex.DecodeString("021c97a90a411ff2b10dc2a8e32de2f29d2fa49d41bfbb52bd416e460db0747d0d")
|
||||
|
||||
defaultServerPubkey, _ = btcec.ParsePubKey(defaultServerPubkeyBytes)
|
||||
|
||||
defaultExpiry = uint32(100)
|
||||
)
|
||||
|
||||
type mockStaticAddressClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
|
||||
in *swapserverrpc.ServerNewAddressRequest, opts ...grpc.CallOption) (
|
||||
*swapserverrpc.ServerNewAddressResponse, error) {
|
||||
|
||||
args := m.Called(ctx, in, opts)
|
||||
|
||||
return args.Get(0).(*swapserverrpc.ServerNewAddressResponse),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
ctxb, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
testContext := NewAddressManagerTestContext(t)
|
||||
|
||||
// Start the manager.
|
||||
go func() {
|
||||
err := testContext.manager.Run(ctxb)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Create the expected static address.
|
||||
expectedAddress, err := GenerateExpectedTaprootAddress(testContext)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a new static address.
|
||||
taprootAddress, err := testContext.manager.NewAddress(ctxb)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The addresses have to match.
|
||||
require.Equal(t, expectedAddress.String(), taprootAddress.String())
|
||||
}
|
||||
|
||||
// GenerateExpectedTaprootAddress generates the expected taproot address that
|
||||
// the predefined parameters are supposed to generate.
|
||||
func GenerateExpectedTaprootAddress(t *ManagerTestContext) (
|
||||
*btcutil.AddressTaproot, error) {
|
||||
|
||||
keyIndex := int32(0)
|
||||
_, pubKey := test.CreateKey(keyIndex)
|
||||
|
||||
keyDescriptor := &keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(swap.StaticAddressKeyFamily),
|
||||
Index: uint32(keyIndex),
|
||||
},
|
||||
PubKey: pubKey,
|
||||
}
|
||||
|
||||
staticAddress, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(defaultExpiry),
|
||||
keyDescriptor.PubKey, defaultServerPubkey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.NewAddressTaproot(
|
||||
schnorr.SerializePubKey(staticAddress.TaprootKey),
|
||||
t.manager.cfg.ChainParams,
|
||||
)
|
||||
}
|
||||
|
||||
// ManagerTestContext is a helper struct that contains all the necessary
|
||||
// components to test the static address manager.
|
||||
type ManagerTestContext struct {
|
||||
manager *Manager
|
||||
context test.Context
|
||||
mockLnd *test.LndMockServices
|
||||
mockStaticAddressClient *mockStaticAddressClient
|
||||
}
|
||||
|
||||
// NewAddressManagerTestContext creates a new test context for the static
|
||||
// address manager.
|
||||
func NewAddressManagerTestContext(t *testing.T) *ManagerTestContext {
|
||||
mockLnd := test.NewMockLnd()
|
||||
lndContext := test.NewContext(t, mockLnd)
|
||||
|
||||
dbFixture := loopdb.NewTestDB(t)
|
||||
|
||||
store := NewSqlStore(dbFixture.BaseDB)
|
||||
|
||||
mockStaticAddressClient := new(mockStaticAddressClient)
|
||||
|
||||
mockStaticAddressClient.On(
|
||||
"ServerNewAddress", mock.Anything, mock.Anything, mock.Anything,
|
||||
).Return(
|
||||
&swapserverrpc.ServerNewAddressResponse{
|
||||
Params: &swapserverrpc.ServerAddressParameters{
|
||||
ServerKey: defaultServerPubkeyBytes,
|
||||
Expiry: defaultExpiry,
|
||||
},
|
||||
}, nil,
|
||||
)
|
||||
|
||||
cfg := &ManagerConfig{
|
||||
Store: store,
|
||||
WalletKit: mockLnd.WalletKit,
|
||||
ChainParams: mockLnd.ChainParams,
|
||||
AddressClient: mockStaticAddressClient,
|
||||
FetchL402: func(context.Context) error { return nil },
|
||||
}
|
||||
|
||||
manager := NewManager(cfg)
|
||||
|
||||
return &ManagerTestContext{
|
||||
manager: manager,
|
||||
context: lndContext,
|
||||
mockLnd: mockLnd,
|
||||
mockStaticAddressClient: mockStaticAddressClient,
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
package deposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/staticaddr/address"
|
||||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/loop/swapserverrpc"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultServerPubkeyBytes, _ = hex.DecodeString("021c97a90a411ff2b10dc2a8e32de2f29d2fa49d41bfbb52bd416e460db0747d0d")
|
||||
|
||||
defaultServerPubkey, _ = btcec.ParsePubKey(defaultServerPubkeyBytes)
|
||||
|
||||
defaultExpiry = uint32(100)
|
||||
|
||||
defaultDepositConfirmations = uint32(3)
|
||||
|
||||
confChan = make(chan *chainntnfs.TxConfirmation)
|
||||
|
||||
confErrChan = make(chan error)
|
||||
|
||||
blockChan = make(chan int32)
|
||||
|
||||
blockErrChan = make(chan error)
|
||||
|
||||
initChan = make(chan struct{})
|
||||
|
||||
finalizedDepositChan = make(chan wire.OutPoint)
|
||||
)
|
||||
|
||||
type mockStaticAddressClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
|
||||
in *swapserverrpc.ServerNewAddressRequest, opts ...grpc.CallOption) (
|
||||
*swapserverrpc.ServerNewAddressResponse, error) {
|
||||
|
||||
args := m.Called(ctx, in, opts)
|
||||
|
||||
return args.Get(0).(*swapserverrpc.ServerNewAddressResponse),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
type mockAddressManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockAddressManager) GetStaticAddressParameters(ctx context.Context) (
|
||||
*address.Parameters, error) {
|
||||
|
||||
args := m.Called(ctx)
|
||||
|
||||
return args.Get(0).(*address.Parameters),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockAddressManager) GetStaticAddress(ctx context.Context) (
|
||||
*script.StaticAddress, error) {
|
||||
|
||||
args := m.Called(ctx)
|
||||
|
||||
return args.Get(0).(*script.StaticAddress),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockAddressManager) ListUnspent(ctx context.Context,
|
||||
minConfs, maxConfs int32) ([]*lnwallet.Utxo, error) {
|
||||
|
||||
args := m.Called(ctx, minConfs, maxConfs)
|
||||
|
||||
return args.Get(0).([]*lnwallet.Utxo),
|
||||
args.Error(1)
|
||||
}
|
||||
|
||||
type mockStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (s *mockStore) CreateDeposit(ctx context.Context, deposit *Deposit) error {
|
||||
args := s.Called(ctx, deposit)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (s *mockStore) UpdateDeposit(ctx context.Context, deposit *Deposit) error {
|
||||
args := s.Called(ctx, deposit)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (s *mockStore) GetDeposit(ctx context.Context, depositID ID) (*Deposit,
|
||||
error) {
|
||||
|
||||
args := s.Called(ctx, depositID)
|
||||
return args.Get(0).(*Deposit), args.Error(1)
|
||||
}
|
||||
|
||||
func (s *mockStore) AllDeposits(ctx context.Context) ([]*Deposit, error) {
|
||||
args := s.Called(ctx)
|
||||
return args.Get(0).([]*Deposit), args.Error(1)
|
||||
}
|
||||
|
||||
type MockChainNotifier struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockChainNotifier) RegisterConfirmationsNtfn(ctx context.Context,
|
||||
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32,
|
||||
_ ...lndclient.NotifierOption) (chan *chainntnfs.TxConfirmation,
|
||||
chan error, error) {
|
||||
|
||||
args := m.Called(ctx, txid, pkScript, numConfs, heightHint)
|
||||
return args.Get(0).(chan *chainntnfs.TxConfirmation),
|
||||
args.Get(1).(chan error), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockChainNotifier) RegisterBlockEpochNtfn(ctx context.Context) (
|
||||
chan int32, chan error, error) {
|
||||
|
||||
args := m.Called(ctx)
|
||||
return args.Get(0).(chan int32), args.Get(1).(chan error), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockChainNotifier) RegisterSpendNtfn(ctx context.Context,
|
||||
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
|
||||
chan *chainntnfs.SpendDetail, chan error, error) {
|
||||
|
||||
args := m.Called(ctx, pkScript, heightHint)
|
||||
return args.Get(0).(chan *chainntnfs.SpendDetail),
|
||||
args.Get(1).(chan error), args.Error(2)
|
||||
}
|
||||
|
||||
// TestManager checks that the manager processes the right channel notifications
|
||||
// while a deposit is expiring.
|
||||
func TestManager(t *testing.T) {
|
||||
ctxb, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create the test context with required mocks.
|
||||
testContext := newManagerTestContext(t)
|
||||
|
||||
// Start the deposit manager.
|
||||
go func() {
|
||||
err := testContext.manager.Run(
|
||||
ctxb, uint32(testContext.mockLnd.Height),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Ensure that the manager has been initialized.
|
||||
<-initChan
|
||||
|
||||
// Notify about the last block before the expiry.
|
||||
blockChan <- int32(defaultDepositConfirmations + defaultExpiry)
|
||||
|
||||
// Ensure that the deposit state machine didn't sign for the expiry tx.
|
||||
select {
|
||||
case <-testContext.mockLnd.SignOutputRawChannel:
|
||||
t.Fatal("received unexpected sign request")
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// Mine the expiry tx height.
|
||||
blockChan <- int32(defaultDepositConfirmations + defaultExpiry)
|
||||
|
||||
// Ensure that the deposit state machine didn't sign for the expiry tx.
|
||||
<-testContext.mockLnd.SignOutputRawChannel
|
||||
|
||||
// Ensure that the signed expiry transaction is published.
|
||||
expiryTx := <-testContext.mockLnd.TxPublishChannel
|
||||
|
||||
// Ensure that the deposit is waiting for a confirmation notification.
|
||||
confChan <- &chainntnfs.TxConfirmation{
|
||||
BlockHeight: defaultDepositConfirmations + defaultExpiry + 3,
|
||||
Tx: expiryTx,
|
||||
}
|
||||
}
|
||||
|
||||
// ManagerTestContext is a helper struct that contains all the necessary
|
||||
// components to test the reservation manager.
|
||||
type ManagerTestContext struct {
|
||||
manager *Manager
|
||||
context test.Context
|
||||
mockLnd *test.LndMockServices
|
||||
mockStaticAddressClient *mockStaticAddressClient
|
||||
mockAddressManager *mockAddressManager
|
||||
}
|
||||
|
||||
// newManagerTestContext creates a new test context for the reservation manager.
|
||||
func newManagerTestContext(t *testing.T) *ManagerTestContext {
|
||||
mockLnd := test.NewMockLnd()
|
||||
lndContext := test.NewContext(t, mockLnd)
|
||||
|
||||
mockStaticAddressClient := new(mockStaticAddressClient)
|
||||
mockAddressManager := new(mockAddressManager)
|
||||
mockStore := new(mockStore)
|
||||
mockChainNotifier := new(MockChainNotifier)
|
||||
|
||||
ID, err := GetRandomDepositID()
|
||||
utxo := &lnwallet.Utxo{
|
||||
AddressType: lnwallet.TaprootPubkey,
|
||||
Value: btcutil.Amount(100000),
|
||||
Confirmations: int64(defaultDepositConfirmations),
|
||||
PkScript: []byte("pkscript"),
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
}
|
||||
require.NoError(t, err)
|
||||
storedDeposits := []*Deposit{
|
||||
{
|
||||
ID: ID,
|
||||
State: Deposited,
|
||||
OutPoint: utxo.OutPoint,
|
||||
Value: utxo.Value,
|
||||
ConfirmationHeight: 3,
|
||||
TimeOutSweepPkScript: []byte{0x42, 0x21, 0x69},
|
||||
},
|
||||
}
|
||||
|
||||
mockStore.On(
|
||||
"AllDeposits", mock.Anything,
|
||||
).Return(storedDeposits, nil)
|
||||
|
||||
mockStore.On(
|
||||
"UpdateDeposit", mock.Anything, mock.Anything,
|
||||
).Return(nil)
|
||||
|
||||
mockAddressManager.On(
|
||||
"GetStaticAddressParameters", mock.Anything,
|
||||
).Return(&address.Parameters{
|
||||
Expiry: defaultExpiry,
|
||||
}, nil)
|
||||
|
||||
mockAddressManager.On(
|
||||
"ListUnspent", mock.Anything, mock.Anything, mock.Anything,
|
||||
).Return([]*lnwallet.Utxo{utxo}, nil)
|
||||
|
||||
// Define the expected return values for the mocks.
|
||||
mockChainNotifier.On(
|
||||
"RegisterConfirmationsNtfn", mock.Anything, mock.Anything,
|
||||
mock.Anything, mock.Anything, mock.Anything,
|
||||
).Return(confChan, confErrChan, nil)
|
||||
|
||||
mockChainNotifier.On("RegisterBlockEpochNtfn", mock.Anything).Return(
|
||||
blockChan, blockErrChan, nil,
|
||||
)
|
||||
|
||||
cfg := &ManagerConfig{
|
||||
AddressClient: mockStaticAddressClient,
|
||||
AddressManager: mockAddressManager,
|
||||
Store: mockStore,
|
||||
WalletKit: mockLnd.WalletKit,
|
||||
ChainParams: mockLnd.ChainParams,
|
||||
ChainNotifier: mockChainNotifier,
|
||||
Signer: mockLnd.Signer,
|
||||
}
|
||||
|
||||
manager := NewManager(cfg)
|
||||
manager.initChan = initChan
|
||||
manager.finalizedDepositChan = finalizedDepositChan
|
||||
|
||||
testContext := &ManagerTestContext{
|
||||
manager: manager,
|
||||
context: lndContext,
|
||||
mockLnd: mockLnd,
|
||||
mockStaticAddressClient: mockStaticAddressClient,
|
||||
mockAddressManager: mockAddressManager,
|
||||
}
|
||||
|
||||
staticAddress := generateStaticAddress(
|
||||
context.Background(), testContext,
|
||||
)
|
||||
mockAddressManager.On(
|
||||
"GetStaticAddress", mock.Anything,
|
||||
).Return(staticAddress, nil)
|
||||
|
||||
return testContext
|
||||
}
|
||||
|
||||
func generateStaticAddress(ctx context.Context,
|
||||
t *ManagerTestContext) *script.StaticAddress {
|
||||
|
||||
keyDescriptor, err := t.mockLnd.WalletKit.DeriveNextKey(
|
||||
ctx, swap.StaticAddressKeyFamily,
|
||||
)
|
||||
require.NoError(t.context.T, err)
|
||||
|
||||
staticAddress, err := script.NewStaticAddress(
|
||||
input.MuSig2Version100RC2, int64(defaultExpiry),
|
||||
keyDescriptor.PubKey, defaultServerPubkey,
|
||||
)
|
||||
require.NoError(t.context.T, err)
|
||||
|
||||
return staticAddress
|
||||
}
|
Loading…
Reference in New Issue