diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 62187d3e5..1782e5a05 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -1632,14 +1632,21 @@ func (s *swapClientServer) NewStaticAddress(ctx context.Context, _ *looprpc.NewStaticAddressRequest) ( *looprpc.NewStaticAddressResponse, error) { - staticAddress, expiry, err := s.staticAddressManager.NewAddress(ctx) + params, err := s.staticAddressManager.NewAddress(ctx) + if err != nil { + return nil, err + } + + address, err := s.staticAddressManager.GetTaprootAddress( + params.ClientPubkey, params.ServerPubkey, int64(params.Expiry), + ) if err != nil { return nil, err } return &looprpc.NewStaticAddressResponse{ - Address: staticAddress.String(), - Expiry: uint32(expiry), + Address: address.String(), + Expiry: params.Expiry, }, nil } diff --git a/staticaddr/address/manager.go b/staticaddr/address/manager.go index e96d362bf..9b9ec85f9 100644 --- a/staticaddr/address/manager.go +++ b/staticaddr/address/manager.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "sync" "sync/atomic" "github.com/btcsuite/btcd/btcec/v2" @@ -49,11 +48,24 @@ type ManagerConfig struct { // Manager manages the address state machines. type Manager struct { - sync.Mutex - cfg *ManagerConfig currentHeight atomic.Int32 + + // addrRequest is a channel used to request new static addresses from + // the manager. The manager employs a go worker routine that handles the + // requests. + addrRequest chan request +} + +type request struct { + ctx context.Context + respChan chan response +} + +type response struct { + parameters *Parameters + err error } // NewManager creates a new address manager. @@ -64,14 +76,48 @@ func NewManager(cfg *ManagerConfig, currentHeight int32) (*Manager, error) { } m := &Manager{ - cfg: cfg, + cfg: cfg, + addrRequest: make(chan request), } m.currentHeight.Store(currentHeight) return m, nil } -// Run runs the address manager. +// addrWorker is a worker that handles address creation requests. It calls +// m.newAddress which blocks on server I/O and returns the address and expiry. +func (m *Manager) addrWorker(ctx context.Context) { + for { + select { + case req := <-m.addrRequest: + m.handleAddrRequest(ctx, req) + + case <-ctx.Done(): + return + } + } +} + +// handleAddrRequest is responsible for processing a single address request. +func (m *Manager) handleAddrRequest(managerCtx context.Context, req request) { + addrParams, err := m.newAddress(req.ctx) + + resp := response{ + parameters: addrParams, + err: err, + } + + select { + case req.respChan <- resp: + + case <-req.ctx.Done(): + + case <-managerCtx.Done(): + } +} + +// Run runs the address manager. It keeps track of the current block height and +// creates new static addresses as needed. func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error { newBlockChan, newBlockErrChan, err := m.cfg.ChainNotifier.RegisterBlockEpochNtfn(ctx) @@ -80,6 +126,10 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error { return err } + // The address worker offloads the address creation with the server to a + // separate go routine. + go m.addrWorker(ctx) + // Communicate to the caller that the address manager has completed its // initialization. close(initChan) @@ -100,49 +150,48 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error { } // NewAddress creates a new static address with the server or returns an -// existing one. -func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, - int64, error) { +// existing one. It now sends a request to the manager's Run loop which +// executes the actual address creation logic. +func (m *Manager) NewAddress(ctx context.Context) (*Parameters, error) { + respChan := make(chan response, 1) + req := request{ + ctx: ctx, + respChan: respChan, + } - // If there's already a static address in the database, we can return - // it. - m.Lock() - addresses, err := m.cfg.Store.GetAllStaticAddresses(ctx) - if err != nil { - m.Unlock() + // Send the new address request to the manager run loop. + select { + case m.addrRequest <- req: - return nil, 0, err + case <-ctx.Done(): + return nil, ctx.Err() } - if len(addresses) > 0 { - clientPubKey := addresses[0].ClientPubkey - serverPubKey := addresses[0].ServerPubkey - expiry := int64(addresses[0].Expiry) - - defer m.Unlock() - - address, err := m.GetTaprootAddress( - clientPubKey, serverPubKey, expiry, - ) - if err != nil { - return nil, 0, err - } - return address, expiry, nil + // Wait for the response from the manager run loop. + select { + case resp := <-respChan: + return resp.parameters, resp.err + + case <-ctx.Done(): + return nil, ctx.Err() } - m.Unlock() +} +// newAddress contains the body of the former NewAddress method and performs the +// actual address creation/lookup according to the requested type. +func (m *Manager) newAddress(ctx context.Context) (*Parameters, error) { // We are fetching a new L402 token from the server. There is one static // address per L402 token allowed. - err = m.cfg.FetchL402(ctx) + err := m.cfg.FetchL402(ctx) if err != nil { - return nil, 0, err + return nil, err } clientPubKey, err := m.cfg.WalletKit.DeriveNextKey( ctx, swap.StaticAddressKeyFamily, ) if err != nil { - return nil, 0, err + return nil, err } // Send our clientPubKey to the server and wait for the server to @@ -155,14 +204,14 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, }, ) if err != nil { - return nil, 0, err + return nil, err } serverParams := resp.GetParams() serverPubKey, err := btcec.ParsePubKey(serverParams.ServerKey) if err != nil { - return nil, 0, err + return nil, err } staticAddress, err := script.NewStaticAddress( @@ -170,12 +219,12 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, clientPubKey.PubKey, serverPubKey, ) if err != nil { - return nil, 0, err + return nil, err } pkScript, err := staticAddress.StaticAddressScript() if err != nil { - return nil, 0, err + return nil, err } // Create the static address from the parameters the server provided and @@ -196,7 +245,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, } err = m.cfg.Store.CreateStaticAddress(ctx, addrParams) if err != nil { - return nil, 0, err + return nil, err } // Import the static address tapscript into our lnd wallet, so we can @@ -206,20 +255,13 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, ) addr, err := m.cfg.WalletKit.ImportTaprootScript(ctx, tapScript) if err != nil { - return nil, 0, err + return nil, err } log.Infof("Imported static address taproot script to lnd wallet: %v", addr) - address, err := m.GetTaprootAddress( - clientPubKey.PubKey, serverPubKey, int64(serverParams.Expiry), - ) - if err != nil { - return nil, 0, err - } - - return address, int64(serverParams.Expiry), nil + return addrParams, nil } // GetTaprootAddress returns a taproot address for the given client and server diff --git a/staticaddr/address/manager_test.go b/staticaddr/address/manager_test.go index c23f246cd..13f257586 100644 --- a/staticaddr/address/manager_test.go +++ b/staticaddr/address/manager_test.go @@ -118,14 +118,19 @@ func TestManager(t *testing.T) { require.NoError(t, err) // Create a new static address. - taprootAddress, expiry, err := testContext.manager.NewAddress(ctxb) + params, err := testContext.manager.NewAddress(ctxb) + require.NoError(t, err) + + address, err := testContext.manager.GetTaprootAddress( + params.ClientPubkey, params.ServerPubkey, int64(params.Expiry), + ) require.NoError(t, err) // The addresses have to match. - require.Equal(t, expectedAddress.String(), taprootAddress.String()) + require.Equal(t, expectedAddress.String(), address.String()) // The expiry has to match. - require.EqualValues(t, defaultExpiry, expiry) + require.EqualValues(t, defaultExpiry, params.Expiry) } // GenerateExpectedTaprootAddress generates the expected taproot address that