mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Update LND, Aperture, and Taproot Assets to revisions using the btcd v2 modules, and update lndclient to v0.21.0-3. Migrate Loop chain, transaction, and address types to their corresponding v2 packages. The lndclient release includes the migration from: https://github.com/lightninglabs/lndclient/pull/280 Taproot Assets is temporarily replaced with its btcd v2 revision because the v0.8 release branch has not adopted the new modules. This raises the minimum Go version to 1.26 and changes exported address types.
331 lines
9.3 KiB
Go
331 lines
9.3 KiB
Go
package address
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
btcaddr "github.com/btcsuite/btcd/address/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/wire/v2"
|
|
"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) (
|
|
*btcaddr.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 btcaddr.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,
|
|
},
|
|
}
|
|
}
|