mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Enable staticcheck's SA1019 check in golangci-lint so deprecated identifiers are caught in CI. Replace deprecated standard library and bbolt APIs with their current equivalents. Keep intentional compatibility reads and writes of deprecated Loop RPC fields behind narrow nolint annotations, because older clients and persisted liquidity parameters still depend on those fields.
331 lines
9.3 KiB
Go
331 lines
9.3 KiB
Go
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/btcsuite/btcd/wire"
|
|
"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) ServerStaticAddressLoopIn(ctx context.Context,
|
|
in *swapserverrpc.ServerStaticAddressLoopInRequest,
|
|
opts ...grpc.CallOption) (
|
|
*swapserverrpc.ServerStaticAddressLoopInResponse, error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
return args.Get(0).(*swapserverrpc.ServerStaticAddressLoopInResponse),
|
|
args.Error(1)
|
|
}
|
|
|
|
func (m *mockStaticAddressClient) PushStaticAddressSweeplessSigs(ctx context.Context,
|
|
in *swapserverrpc.PushStaticAddressSweeplessSigsRequest,
|
|
opts ...grpc.CallOption) (
|
|
*swapserverrpc.PushStaticAddressSweeplessSigsResponse, error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
return args.Get(0).(*swapserverrpc.PushStaticAddressSweeplessSigsResponse),
|
|
args.Error(1)
|
|
}
|
|
|
|
func (m *mockStaticAddressClient) PushStaticAddressHtlcSigs(ctx context.Context,
|
|
in *swapserverrpc.PushStaticAddressHtlcSigsRequest,
|
|
opts ...grpc.CallOption) (
|
|
*swapserverrpc.PushStaticAddressHtlcSigsResponse, error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
return args.Get(0).(*swapserverrpc.PushStaticAddressHtlcSigsResponse),
|
|
args.Error(1)
|
|
}
|
|
|
|
// ServerWithdrawDeposits implements the deprecated RPC required by the
|
|
// generated client interface. Production code uses ServerPsbtWithdrawDeposits.
|
|
//
|
|
//nolint:staticcheck
|
|
func (m *mockStaticAddressClient) ServerWithdrawDeposits(ctx context.Context,
|
|
in *swapserverrpc.ServerWithdrawRequest,
|
|
opts ...grpc.CallOption) (*swapserverrpc.ServerWithdrawResponse,
|
|
error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
return args.Get(0).(*swapserverrpc.ServerWithdrawResponse),
|
|
args.Error(1)
|
|
}
|
|
|
|
func (m *mockStaticAddressClient) ServerPsbtWithdrawDeposits(ctx context.Context,
|
|
in *swapserverrpc.ServerPsbtWithdrawRequest,
|
|
opts ...grpc.CallOption) (*swapserverrpc.ServerPsbtWithdrawResponse,
|
|
error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
return args.Get(0).(*swapserverrpc.ServerPsbtWithdrawResponse),
|
|
args.Error(1)
|
|
}
|
|
|
|
func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
|
|
in *swapserverrpc.ServerNewAddressRequest, opts ...grpc.CallOption) (
|
|
*swapserverrpc.ServerNewAddressResponse, error) {
|
|
|
|
args := m.Called(ctx, in, opts)
|
|
|
|
resp, _ := args.Get(0).(*swapserverrpc.ServerNewAddressResponse)
|
|
|
|
return resp, args.Error(1)
|
|
}
|
|
|
|
// TestManager tests the static address manager generates the corerct static
|
|
// taproot address from the given test parameters.
|
|
func TestManager(t *testing.T) {
|
|
ctxb := t.Context()
|
|
|
|
testContext := NewAddressManagerTestContext(t)
|
|
|
|
// Start the manager.
|
|
initChan := make(chan struct{})
|
|
go func() {
|
|
err := testContext.manager.Run(ctxb, initChan)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
}()
|
|
|
|
<-initChan
|
|
|
|
// Create the expected static address.
|
|
expectedAddress, err := GenerateExpectedTaprootAddress(testContext)
|
|
require.NoError(t, err)
|
|
|
|
// Create a new static address.
|
|
taprootAddress, expiry, err := testContext.manager.NewAddress(ctxb)
|
|
require.NoError(t, err)
|
|
|
|
// The addresses have to match.
|
|
require.Equal(t, expectedAddress.String(), taprootAddress.String())
|
|
|
|
// The expiry has to match.
|
|
require.EqualValues(t, defaultExpiry, expiry)
|
|
}
|
|
|
|
// TestNewAddressValidatesServerResponse tests that the untrusted
|
|
// ServerNewAddress response is validated before the address script is created.
|
|
func TestNewAddressValidatesServerResponse(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
resp *swapserverrpc.ServerNewAddressResponse
|
|
expected string
|
|
}{
|
|
{
|
|
name: "nil response",
|
|
expected: "missing server new address response",
|
|
},
|
|
{
|
|
name: "nil params",
|
|
resp: &swapserverrpc.ServerNewAddressResponse{},
|
|
expected: "missing server address parameters",
|
|
},
|
|
{
|
|
name: "missing server key",
|
|
resp: &swapserverrpc.ServerNewAddressResponse{
|
|
Params: &swapserverrpc.ServerAddressParameters{
|
|
Expiry: defaultExpiry,
|
|
},
|
|
},
|
|
expected: "missing server public key",
|
|
},
|
|
{
|
|
name: "uncompressed server key",
|
|
resp: &swapserverrpc.ServerNewAddressResponse{
|
|
Params: &swapserverrpc.ServerAddressParameters{
|
|
ServerKey: []byte{0x04},
|
|
Expiry: defaultExpiry,
|
|
},
|
|
},
|
|
expected: "server public key is not a compressed",
|
|
},
|
|
{
|
|
name: "zero expiry",
|
|
resp: newServerNewAddressResponse(0),
|
|
expected: "static address CSV expiry must be non-zero",
|
|
},
|
|
{
|
|
name: "seconds flag",
|
|
resp: newServerNewAddressResponse(
|
|
wire.SequenceLockTimeIsSeconds | 1,
|
|
),
|
|
expected: "static address expiry does not fit into CSV",
|
|
},
|
|
{
|
|
name: "disabled flag",
|
|
resp: newServerNewAddressResponse(
|
|
wire.SequenceLockTimeDisabled | 1,
|
|
),
|
|
expected: "static address expiry does not fit into CSV",
|
|
},
|
|
{
|
|
name: "reserved flag",
|
|
resp: newServerNewAddressResponse(
|
|
wire.SequenceLockTimeMask + 1,
|
|
),
|
|
expected: "static address expiry does not fit into CSV",
|
|
},
|
|
{
|
|
name: "too large",
|
|
resp: newServerNewAddressResponse(
|
|
maxStaticAddressCSVExpiry + 1,
|
|
),
|
|
expected: "exceeds maximum",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
testContext := NewAddressManagerTestContextWithResponse(
|
|
t, test.resp,
|
|
)
|
|
|
|
_, _, err := testContext.manager.NewAddress(t.Context())
|
|
require.ErrorContains(t, err, test.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewAddressAcceptsMaxCSVExpiry tests the upper valid CSV boundary.
|
|
func TestNewAddressAcceptsMaxCSVExpiry(t *testing.T) {
|
|
testContext := NewAddressManagerTestContextWithResponse(
|
|
t, newServerNewAddressResponse(maxStaticAddressCSVExpiry),
|
|
)
|
|
|
|
_, expiry, err := testContext.manager.NewAddress(t.Context())
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, maxStaticAddressCSVExpiry, expiry)
|
|
}
|
|
|
|
// 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 {
|
|
return NewAddressManagerTestContextWithResponse(
|
|
t, newServerNewAddressResponse(defaultExpiry),
|
|
)
|
|
}
|
|
|
|
// NewAddressManagerTestContextWithResponse creates a new test context with a
|
|
// custom ServerNewAddress response.
|
|
func NewAddressManagerTestContextWithResponse(t *testing.T,
|
|
resp *swapserverrpc.ServerNewAddressResponse) *ManagerTestContext {
|
|
|
|
ctxb, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
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(resp, nil)
|
|
|
|
cfg := &ManagerConfig{
|
|
Store: store,
|
|
WalletKit: mockLnd.WalletKit,
|
|
ChainParams: mockLnd.ChainParams,
|
|
AddressClient: mockStaticAddressClient,
|
|
ChainNotifier: mockLnd.ChainNotifier,
|
|
FetchL402: func(context.Context) error { return nil },
|
|
}
|
|
|
|
getInfo, err := mockLnd.Client.GetInfo(ctxb)
|
|
require.NoError(t, err)
|
|
|
|
manager, err := NewManager(cfg, int32(getInfo.BlockHeight))
|
|
require.NoError(t, err)
|
|
|
|
return &ManagerTestContext{
|
|
manager: manager,
|
|
context: lndContext,
|
|
mockLnd: mockLnd,
|
|
mockStaticAddressClient: mockStaticAddressClient,
|
|
}
|
|
}
|
|
|
|
// newServerNewAddressResponse returns a valid server response with the given
|
|
// CSV expiry.
|
|
func newServerNewAddressResponse(expiry uint32) *swapserverrpc.ServerNewAddressResponse {
|
|
return &swapserverrpc.ServerNewAddressResponse{
|
|
Params: &swapserverrpc.ServerAddressParameters{
|
|
ServerKey: defaultServerPubkeyBytes,
|
|
Expiry: expiry,
|
|
},
|
|
}
|
|
}
|