From 696cacd3b031ec8a1dd4aa7a2d56ddd99943b7be Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 13 Jul 2023 16:01:59 +0200 Subject: [PATCH] chainnotifier: add re-org chan option to RegisterConfirmationsNtfn With this commit we add the WithReOrgChan functional option to the RegisterConfirmationsNtfn method that allows a caller to be notified about a chain re-organization of a transaction. Using WithReOrgChan() might be a breaking change to the caller, as the behavior of the RegisterConfirmationsNtfn changes: Before, the notification channel would not continue sending updates after a confirmation event was received. When enabling WithReOrgChan(), the notification channel will continue sending updates even after a first confirmation, and will also send on the provided re-org channel if a re-org happens. After a re-org a new confirmation event is sent on the notification channel once the transaction is included in another block again. To allow a caller to reliably make sure a transaction reaches a certain safe depth in the chain, it is recommended to use RegisterConfirmationsNtfn with numConfs=1 but then keep the notification channel open until the transaction reaches the desired number of confirmations (or reac to re-org events if they happen). --- chainnotifier_client.go | 62 ++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/chainnotifier_client.go b/chainnotifier_client.go index 5195b6d..744303d 100644 --- a/chainnotifier_client.go +++ b/chainnotifier_client.go @@ -19,6 +19,14 @@ type notifierOptions struct { // includeBlock if true, then the dispatched confirmation notification // will include the block that mined the transaction. includeBlock bool + + // 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{} } // defaultNotifierOptions returns the set of default options for the notifier. @@ -38,6 +46,18 @@ func WithIncludeBlock() NotifierOption { } } +// WithReOrgChan configures a channel that 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. +func WithReOrgChan(reOrgChan chan struct{}) NotifierOption { + return func(o *notifierOptions) { + o.reOrgChan = reOrgChan + } +} + // ChainNotifierClient exposes base lightning functionality. type ChainNotifierClient interface { RegisterBlockEpochNtfn(ctx context.Context) ( @@ -45,8 +65,8 @@ type ChainNotifierClient interface { RegisterConfirmationsNtfn(ctx context.Context, txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32, - opts ...NotifierOption) ( - chan *chainntnfs.TxConfirmation, chan error, error) + opts ...NotifierOption) (chan *chainntnfs.TxConfirmation, + chan error, error) RegisterSpendNtfn(ctx context.Context, outpoint *wire.OutPoint, pkScript []byte, heightHint int32) ( @@ -153,8 +173,8 @@ func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context, func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32, - optFuncs ...NotifierOption) ( - chan *chainntnfs.TxConfirmation, chan error, error) { + optFuncs ...NotifierOption) (chan *chainntnfs.TxConfirmation, + chan error, error) { opts := defaultNotifierOptions() for _, optFunc := range optFuncs { @@ -166,8 +186,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, txidSlice = txid[:] } confStream, err := s.client.RegisterConfirmationsNtfn( - s.chainMac.WithMacaroonAuth(ctx), - &chainrpc.ConfRequest{ + s.chainMac.WithMacaroonAuth(ctx), &chainrpc.ConfRequest{ Script: pkScript, NumConfs: uint32(numConfs), HeightHint: uint32(heightHint), @@ -195,7 +214,7 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, } switch c := confEvent.Event.(type) { - // Script confirmed + // Script confirmed. case *chainrpc.ConfEvent_Conf: tx, err := decodeTx(c.Conf.RawTx) if err != nil { @@ -205,7 +224,9 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, var block *wire.MsgBlock if opts.includeBlock { - block, err = decodeBlock(c.Conf.RawBlock) + block, err = decodeBlock( + c.Conf.RawBlock, + ) if err != nil { errChan <- err return @@ -227,10 +248,26 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, TxIndex: c.Conf.TxIndex, Block: block, } - return - // Ignore reorg events, not supported. + // 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 + } + + // On a re-org, we just need to signal, we don't have + // 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 { + select { + case opts.reOrgChan <- struct{}{}: + case <-ctx.Done(): + return + } + } continue // Nil event, should never happen. @@ -240,9 +277,8 @@ func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context, // Unexpected type. default: - errChan <- fmt.Errorf( - "conf event has unexpected type", - ) + errChan <- fmt.Errorf("conf event has " + + "unexpected type") return } }