From cb46084eaf2c350e9c3887a89bed26b30b64c766 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Fri, 27 Jun 2025 20:08:29 +0200 Subject: [PATCH 1/3] lndclient: make notifier options exported (to help mocking) --- chainnotifier_client.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/chainnotifier_client.go b/chainnotifier_client.go index 0c7b088a..37d7b747 100644 --- a/chainnotifier_client.go +++ b/chainnotifier_client.go @@ -13,36 +13,36 @@ import ( "google.golang.org/grpc" ) -// notifierOptions is a set of functional options that allow callers to further +// NotifierOptions is a set of functional options that allow callers to further // modify the type of chain even notifications they receive. -type notifierOptions struct { - // includeBlock if true, then the dispatched confirmation notification +type NotifierOptions struct { + // IncludeBlock if true, then the dispatched confirmation notification // will include the block that mined the transaction. - includeBlock bool + IncludeBlock bool - // reOrgChan if set, will be sent on if the transaction is re-organized + // ReOrgChan if set, will be sent on if the transaction is re-organized // out of the chain. This channel being set will also imply that we // don't cancel the notification listener after having received one // confirmation event. That means the caller manually needs to cancel // the passed in context to cancel being notified once the required // number of confirmations have been reached. - reOrgChan chan struct{} + ReOrgChan chan struct{} } // defaultNotifierOptions returns the set of default options for the notifier. -func defaultNotifierOptions() *notifierOptions { - return ¬ifierOptions{} +func DefaultNotifierOptions() *NotifierOptions { + return &NotifierOptions{} } // NotifierOption is a functional option that allows a caller to modify the // events received from the notifier. -type NotifierOption func(*notifierOptions) +type NotifierOption func(*NotifierOptions) // WithIncludeBlock is an optional argument that allows the caller to specify // that the block that mined a transaction should be included in the response. func WithIncludeBlock() NotifierOption { - return func(o *notifierOptions) { - o.includeBlock = true + return func(o *NotifierOptions) { + o.IncludeBlock = true } } @@ -53,8 +53,8 @@ func WithIncludeBlock() NotifierOption { // to cancel being notified once the required number of confirmations have been // reached. func WithReOrgChan(reOrgChan chan struct{}) NotifierOption { - return func(o *notifierOptions) { - o.reOrgChan = reOrgChan + return func(o *NotifierOptions) { + o.ReOrgChan = reOrgChan } } @@ -191,7 +191,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, optFuncs ...NotifierOption) (chan *chainntnfs.TxConfirmation, chan error, error) { - opts := defaultNotifierOptions() + opts := DefaultNotifierOptions() for _, optFunc := range optFuncs { optFunc(opts) } @@ -206,7 +206,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, NumConfs: uint32(numConfs), HeightHint: uint32(heightHint), Txid: txidSlice, - IncludeBlock: opts.includeBlock, + IncludeBlock: opts.IncludeBlock, }, ) if err != nil { @@ -238,7 +238,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, } var block *wire.MsgBlock - if opts.includeBlock { + if opts.IncludeBlock { block, err = decodeBlock( c.Conf.RawBlock, ) @@ -268,7 +268,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, // we don't return here, since we might want to // be informed about the new block we got // confirmed in after a re-org. - if opts.reOrgChan == nil { + if opts.ReOrgChan == nil { return } @@ -276,9 +276,9 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, // any additional information. But we only signal if the // caller requested to be notified about re-orgs. case *chainrpc.ConfEvent_Reorg: - if opts.reOrgChan != nil { + if opts.ReOrgChan != nil { select { - case opts.reOrgChan <- struct{}{}: + case opts.ReOrgChan <- struct{}{}: case <-ctx.Done(): return } From 692014f74effe0d110fd7d0411c0bce584118573 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 8 Jul 2025 20:00:32 -0300 Subject: [PATCH 2/3] RegisterSpendNtfn: support reorg channel The corresponding API of LND already supports notifying the caller about a reorg. I reused NotifierOption's used for RegisterConfirmationsNtfn already to pass a channel notified upon a reorg affecting a reported confirmation. --- chainnotifier_client.go | 58 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/chainnotifier_client.go b/chainnotifier_client.go index 37d7b747..63ed4c78 100644 --- a/chainnotifier_client.go +++ b/chainnotifier_client.go @@ -71,8 +71,9 @@ type ChainNotifierClient interface { chan error, error) RegisterSpendNtfn(ctx context.Context, - outpoint *wire.OutPoint, pkScript []byte, heightHint int32) ( - chan *chainntnfs.SpendDetail, chan error, error) + outpoint *wire.OutPoint, pkScript []byte, heightHint int32, + optFuncs ...NotifierOption) (chan *chainntnfs.SpendDetail, + chan error, error) } type chainNotifierClient struct { @@ -111,8 +112,18 @@ func (s *chainNotifierClient) RawClientWithMacAuth( } func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, - outpoint *wire.OutPoint, pkScript []byte, heightHint int32) ( - chan *chainntnfs.SpendDetail, chan error, error) { + outpoint *wire.OutPoint, pkScript []byte, heightHint int32, + optFuncs ...NotifierOption) (chan *chainntnfs.SpendDetail, chan error, + error) { + + opts := DefaultNotifierOptions() + for _, optFunc := range optFuncs { + optFunc(opts) + } + if opts.IncludeBlock { + return nil, nil, fmt.Errorf("option IncludeBlock is not " + + "supported by RegisterSpendNtfn") + } var rpcOutpoint *chainrpc.Outpoint if outpoint != nil { @@ -162,6 +173,18 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, return nil } + processReorg := func() { + if opts.ReOrgChan == nil { + return + } + + select { + case opts.ReOrgChan <- struct{}{}: + case <-ctx.Done(): + return + } + } + s.wg.Add(1) go func() { defer s.wg.Done() @@ -172,12 +195,35 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, return } - c, ok := spendEvent.Event.(*chainrpc.SpendEvent_Spend) - if ok { + switch c := spendEvent.Event.(type) { + case *chainrpc.SpendEvent_Spend: err := processSpendDetail(c.Spend) if err != nil { errChan <- err + + return } + + // If we're running in re-org aware mode, then + // we don't return here, since we might want to + // be informed about the new block we got + // confirmed in after a re-org. + if opts.ReOrgChan == nil { + return + } + + case *chainrpc.SpendEvent_Reorg: + processReorg() + + // Nil event, should never happen. + case nil: + errChan <- fmt.Errorf("spend event empty") + return + + // Unexpected type. + default: + errChan <- fmt.Errorf("spend event has " + + "unexpected type") return } } From 4eca8b789a76843f559fcc0ca0d5c2833b841e56 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 9 Jul 2025 03:01:57 -0300 Subject: [PATCH 3/3] listen for ctx expiry when sending spend and conf Avoid a deadlock in case the caller doesn't consume notifications about spending or confirmations after cancelling a call. --- chainnotifier_client.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/chainnotifier_client.go b/chainnotifier_client.go index 63ed4c78..4e589f60 100644 --- a/chainnotifier_client.go +++ b/chainnotifier_client.go @@ -159,7 +159,7 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, if err != nil { return err } - spendChan <- &chainntnfs.SpendDetail{ + spend := &chainntnfs.SpendDetail{ SpentOutPoint: &wire.OutPoint{ Hash: *outpointHash, Index: d.SpendingOutpoint.Index, @@ -170,7 +170,12 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, SpendingHeight: int32(d.SpendingHeight), } - return nil + select { + case spendChan <- spend: + return nil + case <-ctx.Done(): + return ctx.Err() + } } processReorg := func() { @@ -302,7 +307,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, return } - confChan <- &chainntnfs.TxConfirmation{ + conf := &chainntnfs.TxConfirmation{ BlockHeight: c.Conf.BlockHeight, BlockHash: blockHash, Tx: tx, @@ -310,6 +315,12 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, Block: block, } + select { + case confChan <- conf: + case <-ctx.Done(): + return + } + // If we're running in re-org aware mode, then // we don't return here, since we might want to // be informed about the new block we got