From 08f1c2e93ac6ff6a1eb7d7d81e401b3e44fc7b00 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 13 Jul 2022 19:27:10 -0700 Subject: [PATCH 1/4] chainntfns: add new option for conf notifications to send block In this commit, we add a new option for the existing confirmation notification system that optionally allows the caller to specify that a block should be included as well. The only quirk w/ the implementation here is the neutrino backend: usually we get filtered blocks, we so need to first fetch the block again so we can deliver the full block to the notifier. On the notifier end, it'll only be checking for the transactions we care about, to sending a full block doesn't affect the correctness. We also extend the `testBatchConfirmationNotification` test to assert that a block is only included if the caller specifies it. --- chainntnfs/bitcoindnotify/bitcoind.go | 11 ++-- chainntnfs/btcdnotify/btcd.go | 16 ++--- chainntnfs/interface.go | 63 ++++++++++++++++--- chainntnfs/neutrinonotify/neutrino.go | 25 +++++--- chainntnfs/test/test_interface.go | 30 ++++++++- chainntnfs/txnotifier.go | 82 ++++++++++++++++++------ chainntnfs/txnotifier_test.go | 90 +++++++++++---------------- chainreg/no_chain_backend.go | 3 +- discovery/gossiper_test.go | 3 +- funding/manager_test.go | 2 +- lntest/mock/chainnotifier.go | 4 +- sweep/test_utils.go | 4 +- 12 files changed, 224 insertions(+), 109 deletions(-) diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index a83b26e06..424291351 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -564,6 +564,7 @@ func (b *BitcoindNotifier) confDetailsManually(confRequest chainntnfs.ConfReques BlockHash: blockHash, BlockHeight: height, TxIndex: uint32(txIndex), + Block: block, }, chainntnfs.TxFoundManually, nil } } @@ -584,12 +585,12 @@ func (b *BitcoindNotifier) handleBlockConnected(block chainntnfs.BlockEpoch) err if err != nil { return fmt.Errorf("unable to get block: %v", err) } - txns := btcutil.NewBlock(rawBlock).Transactions() + utilBlock := btcutil.NewBlock(rawBlock) // We'll then extend the txNotifier's height with the information of // this new block, which will handle all of the notification logic for // us. - err = b.txNotifier.ConnectTip(block.Hash, uint32(block.Height), txns) + err = b.txNotifier.ConnectTip(utilBlock, uint32(block.Height)) if err != nil { return fmt.Errorf("unable to connect tip: %v", err) } @@ -844,15 +845,15 @@ func (b *BitcoindNotifier) historicalSpendDetails( // channel. Once it has reached all of its confirmations, a notification will be // sent across the 'Confirmed' channel. func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - pkScript []byte, - numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + pkScript []byte, numConfs, heightHint uint32, + opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { // Register the conf notification with the TxNotifier. A non-nil value // for `dispatch` will be returned if we are required to perform a // manual scan for the confirmation. Otherwise the notifier will begin // watching at tip for the transaction to confirm. ntfn, err := b.txNotifier.RegisterConf( - txid, pkScript, numConfs, heightHint, + txid, pkScript, numConfs, heightHint, opts..., ) if err != nil { return nil, err diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 335adfa90..2ced59754 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -263,13 +263,14 @@ func (b *BtcdNotifier) onBlockConnected(hash *chainhash.Hash, height int32, t ti // chain. The slice of transactions will only be populated if the block // includes a transaction that confirmed one of our watched txids, or spends // one of the outputs currently being watched. +// // TODO(halseth): this is currently used for complete blocks. Change to use // onFilteredBlockConnected and onFilteredBlockDisconnected, making it easier // to unify with the Neutrino implementation. type filteredBlock struct { hash chainhash.Hash height uint32 - txns []*btcutil.Tx + block *btcutil.Block // connected is true if this update is a new block and false if it is a // disconnected block. @@ -619,6 +620,7 @@ func (b *BtcdNotifier) confDetailsManually(confRequest chainntnfs.ConfRequest, BlockHash: blockHash, BlockHeight: height, TxIndex: uint32(txIndex), + Block: block, }, chainntnfs.TxFoundManually, nil } } @@ -644,16 +646,14 @@ func (b *BtcdNotifier) handleBlockConnected(epoch chainntnfs.BlockEpoch) error { newBlock := &filteredBlock{ hash: *epoch.Hash, height: uint32(epoch.Height), - txns: btcutil.NewBlock(rawBlock).Transactions(), + block: btcutil.NewBlock(rawBlock), connect: true, } // We'll then extend the txNotifier's height with the information of // this new block, which will handle all of the notification logic for // us. - err = b.txNotifier.ConnectTip( - &newBlock.hash, newBlock.height, newBlock.txns, - ) + err = b.txNotifier.ConnectTip(newBlock.block, newBlock.height) if err != nil { return fmt.Errorf("unable to connect tip: %v", err) } @@ -903,15 +903,15 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, // channel. Once it has reached all of its confirmations, a notification will be // sent across the 'Confirmed' channel. func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - pkScript []byte, - numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + pkScript []byte, numConfs, heightHint uint32, + opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { // Register the conf notification with the TxNotifier. A non-nil value // for `dispatch` will be returned if we are required to perform a // manual scan for the confirmation. Otherwise the notifier will begin // watching at tip for the transaction to confirm. ntfn, err := b.txNotifier.RegisterConf( - txid, pkScript, numConfs, heightHint, + txid, pkScript, numConfs, heightHint, opts..., ) if err != nil { return nil, err diff --git a/chainntnfs/interface.go b/chainntnfs/interface.go index 724d77094..360dcd455 100644 --- a/chainntnfs/interface.go +++ b/chainntnfs/interface.go @@ -8,7 +8,9 @@ import ( "strings" "sync" + "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -67,6 +69,31 @@ func (t TxConfStatus) String() string { } } +// notifierOptions is a set of functional options that allow callers to further +// modify the type of chain event notifications they receive. +type notifierOptions struct { + // includeBlock if true, then the dispatched confirmation notification + // will include the block that mined the transaction. + includeBlock bool +} + +// defaultNotifierOptions returns the set of default options for the notifier. +func defaultNotifierOptions() *notifierOptions { + return ¬ifierOptions{} +} + +// NotifierOption is a functional option that allows a caller to modify the +// events received from the notifier. +type NotifierOption func(*notifierOptions) + +// WithIncludeBlock is an optional argument that allows the calelr 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 + } +} + // ChainNotifier represents a trusted source to receive notifications concerning // targeted events on the Bitcoin blockchain. The interface specification is // intentionally general in order to support a wide array of chain notification @@ -97,7 +124,8 @@ type ChainNotifier interface { // NOTE: Dispatching notifications to multiple clients subscribed to // the same (txid, numConfs) tuple MUST be supported. RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte, - numConfs, heightHint uint32) (*ConfirmationEvent, error) + numConfs, heightHint uint32, + opts ...NotifierOption) (*ConfirmationEvent, error) // RegisterSpendNtfn registers an intent to be notified once the target // outpoint is successfully spent within a transaction. The script that @@ -166,6 +194,12 @@ type TxConfirmation struct { // Tx is the transaction for which the notification was requested for. Tx *wire.MsgTx + + // Block is the block that contains the transaction referenced above. + // + // NOTE: This is only specified if the confirmation request opts to + // have the response include the block itself. + Block *wire.MsgBlock } // ConfirmationEvent encapsulates a confirmation notification. With this struct, @@ -628,9 +662,8 @@ type TxIndexConn interface { // block that the transaction confirmed. GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error) - // GetBlockVerbose returns the block identified by the chain hash along - // with additional information such as the block's height in the chain. - GetBlockVerbose(*chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) + // GetBlock returns the block identified by the chain hash. + GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) } // ConfDetailsFromTxIndex looks up whether a transaction is already included in @@ -700,26 +733,38 @@ func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest, fmt.Errorf("unable to get block hash %v for "+ "historical dispatch: %v", rawTxRes.BlockHash, err) } - block, err := chainConn.GetBlockVerbose(blockHash) + block, err := chainConn.GetBlock(blockHash) if err != nil { return nil, TxNotFoundIndex, fmt.Errorf("unable to get block with hash %v for "+ "historical dispatch: %v", blockHash, err) } + // In the modern chain (the only one we really care about for LN), the + // coinbase transaction of all blocks will include the block height. + // Therefore we can save another query, and just use that height + // directly. + blockHeight, err := blockchain.ExtractCoinbaseHeight( + btcutil.NewTx(block.Transactions[0]), + ) + if err != nil { + return nil, TxNotFoundIndex, fmt.Errorf("unable to extract "+ + "coinbase height: %w", err) + } + // If the block was obtained, locate the transaction's index within the // block so we can give the subscriber full confirmation details. - txidStr := r.TxID.String() - for txIndex, txHash := range block.Tx { - if txHash != txidStr { + for txIndex, blockTx := range block.Transactions { + if blockTx.TxHash() != r.TxID { continue } return &TxConfirmation{ Tx: &tx, BlockHash: blockHash, - BlockHeight: uint32(block.Height), + BlockHeight: uint32(blockHeight), TxIndex: uint32(txIndex), + Block: block, }, TxFoundIndex, nil } diff --git a/chainntnfs/neutrinonotify/neutrino.go b/chainntnfs/neutrinonotify/neutrino.go index 35d789e11..b8286e424 100644 --- a/chainntnfs/neutrinonotify/neutrino.go +++ b/chainntnfs/neutrinonotify/neutrino.go @@ -636,6 +636,7 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ BlockHash: blockHash, BlockHeight: scanHeight, TxIndex: uint32(i), + Block: block.MsgBlock(), }, nil } } @@ -649,11 +650,19 @@ func (n *NeutrinoNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ // // NOTE: This method must be called with the bestBlockMtx lock held. func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error { - // We'll extend the txNotifier's height with the information of this new - // block, which will handle all of the notification logic for us. - err := n.txNotifier.ConnectTip( - &newBlock.hash, newBlock.height, newBlock.txns, - ) + // We'll extend the txNotifier's height with the information of this + // new block, which will handle all of the notification logic for us. + // + // We actually need the _full_ block here as well in order to be able + // to send the full block back up to the client. The neutrino client + // itself will only dispatch a block if one of the items we're looking + // for matches, so ultimately passing it the full block will still only + // result in the items we care about being dispatched. + rawBlock, err := n.GetBlock(newBlock.hash) + if err != nil { + return fmt.Errorf("unable to get full block: %v", err) + } + err = n.txNotifier.ConnectTip(rawBlock, newBlock.height) if err != nil { return fmt.Errorf("unable to connect tip: %v", err) } @@ -899,15 +908,15 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, // channel. Once it has reached all of its confirmations, a notification will be // sent across the 'Confirmed' channel. func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - pkScript []byte, - numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + pkScript []byte, numConfs, heightHint uint32, + opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { // Register the conf notification with the TxNotifier. A non-nil value // for `dispatch` will be returned if we are required to perform a // manual scan for the confirmation. Otherwise the notifier will begin // watching at tip for the transaction to confirm. ntfn, err := n.txNotifier.RegisterConf( - txid, pkScript, numConfs, heightHint, + txid, pkScript, numConfs, heightHint, opts..., ) if err != nil { return nil, err diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go index 7faf468b1..3f81e77de 100644 --- a/chainntnfs/test/test_interface.go +++ b/chainntnfs/test/test_interface.go @@ -160,6 +160,13 @@ func testBatchConfirmationNotification(miner *rpctest.Harness, // verify they're each notified at the proper number of confirmations // below. for i, numConfs := range confSpread { + // All the clients with an even index will ask for the block + // along side the conf ntfn. + var opts []chainntnfs.NotifierOption + if i%2 == 0 { + opts = append(opts, chainntnfs.WithIncludeBlock()) + } + txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner) if err != nil { t.Fatalf("unable to create test addr: %v", err) @@ -168,10 +175,12 @@ func testBatchConfirmationNotification(miner *rpctest.Harness, if scriptDispatch { confIntent, err = notifier.RegisterConfirmationsNtfn( nil, pkScript, numConfs, uint32(currentHeight), + opts..., ) } else { confIntent, err = notifier.RegisterConfirmationsNtfn( txid, pkScript, numConfs, uint32(currentHeight), + opts..., ) } if err != nil { @@ -218,6 +227,12 @@ func testBatchConfirmationNotification(miner *rpctest.Harness, "conf height: expected %v, got %v", initialConfHeight, conf.BlockHeight) } + + // If this is an even client index, then we expect the + // block to be populated. Otherwise, it should be + // empty. + expectBlock := i%2 == 0 + require.Equal(t, expectBlock, conf.Block != nil) continue case <-time.After(20 * time.Second): t.Fatalf("confirmation notification never received: %v", numConfs) @@ -547,6 +562,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, if scriptDispatch { ntfn1, err = notifier.RegisterConfirmationsNtfn( nil, pkScript1, 1, uint32(currentHeight), + chainntnfs.WithIncludeBlock(), ) } else { ntfn1, err = notifier.RegisterConfirmationsNtfn( @@ -579,6 +595,13 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, t.Fatalf("incorrect block height: expected %v, got %v", confInfo.BlockHeight, currentHeight) } + + // Ensure that if this was a script dispatch, the block is set + // as well. + if scriptDispatch { + require.NotNil(t, confInfo.Block) + } + break case <-time.After(20 * time.Second): t.Fatalf("confirmation notification never received") @@ -591,6 +614,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, if scriptDispatch { ntfn2, err = notifier.RegisterConfirmationsNtfn( nil, pkScript2, 3, uint32(currentHeight), + chainntnfs.WithIncludeBlock(), ) } else { ntfn2, err = notifier.RegisterConfirmationsNtfn( @@ -622,6 +646,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, if scriptDispatch { ntfn3, err = notifier.RegisterConfirmationsNtfn( nil, pkScript3, 1, uint32(currentHeight-1), + chainntnfs.WithIncludeBlock(), ) } else { ntfn3, err = notifier.RegisterConfirmationsNtfn( @@ -640,7 +665,10 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, require.NoError(t, err, "unable to register ntfn") select { - case <-ntfn3.Confirmed: + case confInfo := <-ntfn3.Confirmed: + if scriptDispatch { + require.NotNil(t, confInfo.Block) + } case <-time.After(10 * time.Second): t.Fatalf("confirmation notification never received") } diff --git a/chainntnfs/txnotifier.go b/chainntnfs/txnotifier.go index 409002346..01840a063 100644 --- a/chainntnfs/txnotifier.go +++ b/chainntnfs/txnotifier.go @@ -265,6 +265,10 @@ type ConfNtfn struct { // dispatched is false if the confirmed notification has not been sent yet. dispatched bool + + // includeBlock is true if the dispatched notification should also have + // the block included with it. + includeBlock bool } // HistoricalConfDispatch parametrizes a manual rescan for a particular @@ -576,7 +580,8 @@ func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32, // newConfNtfn validates all of the parameters required to successfully create // and register a confirmation notification. func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, - pkScript []byte, numConfs, heightHint uint32) (*ConfNtfn, error) { + pkScript []byte, numConfs, heightHint uint32, + opts *notifierOptions) (*ConfNtfn, error) { // An accompanying output script must always be provided. if len(pkScript) == 0 { @@ -609,7 +614,8 @@ func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, Event: NewConfirmationEvent(numConfs, func() { n.CancelConf(confRequest, confID) }), - HeightHint: heightHint, + HeightHint: heightHint, + includeBlock: opts.includeBlock, }, nil } @@ -622,7 +628,8 @@ func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, // UpdateConfDetails method, otherwise we will wait for the transaction/output // script to confirm even though it already has. func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, - numConfs, heightHint uint32) (*ConfRegistration, error) { + numConfs, heightHint uint32, + optFuncs ...NotifierOption) (*ConfRegistration, error) { select { case <-n.quit: @@ -630,8 +637,13 @@ func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, default: } + opts := defaultNotifierOptions() + for _, optFunc := range optFuncs { + optFunc(opts) + } + // We'll start by performing a series of validation checks. - ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint) + ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts) if err != nil { return nil, err } @@ -682,6 +694,14 @@ func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte, "registration since rescan has finished", ntfn.ConfRequest) + // The default notification we assigned above includes the + // block along with the rest of the details. However not all + // clients want the block, so we make a copy here w/o the block + // if needed so we can give clients only what they ask for. + if !ntfn.includeBlock && confSet.details != nil { + confSet.details.Block = nil + } + err := n.dispatchConfDetails(ntfn, confSet.details) if err != nil { return nil, err @@ -888,7 +908,16 @@ func (n *TxNotifier) UpdateConfDetails(confRequest ConfRequest, // notifications that have not yet been delivered. confSet.details = details for _, ntfn := range confSet.ntfns { - err = n.dispatchConfDetails(ntfn, details) + // The default notification we assigned above includes the + // block along with the rest of the details. However not all + // clients want the block, so we make a copy here w/o the block + // if needed so we can give clients only what they ask for. + confDetails := *details + if !ntfn.includeBlock { + confDetails.Block = nil + } + + err = n.dispatchConfDetails(ntfn, &confDetails) if err != nil { return err } @@ -1207,7 +1236,7 @@ func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx, onSpend := func(request SpendRequest, details *SpendDetail) { spends = append(spends, spend{&request, details}) } - n.filterTx(tx, nil, blockHeight, nil, onSpend) + n.filterTx(nil, tx, blockHeight, nil, onSpend) // After the transaction has been filtered, we can finally dispatch // notifications for each request. @@ -1391,8 +1420,8 @@ func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) // NOTE: In order to actually dispatch the relevant transaction notifications to // clients, NotifyHeight must be called with the same block height in order to // maintain correctness. -func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32, - txns []*btcutil.Tx) error { +func (n *TxNotifier) ConnectTip(block *btcutil.Block, + blockHeight uint32) error { select { case <-n.quit: @@ -1413,13 +1442,18 @@ func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32, // First, we'll iterate over all the transactions found in this block to // determine if it includes any relevant transactions to the TxNotifier. - Log.Debugf("Filtering %d txns for %d spend requests at height %d", - len(txns), len(n.spendNotifications), blockHeight) - for _, tx := range txns { - n.filterTx( - tx, blockHash, blockHeight, n.handleConfDetailsAtTip, - n.handleSpendDetailsAtTip, - ) + if block != nil { + Log.Debugf("Filtering %d txns for %d spend requests at "+ + "height %d", len(block.Transactions()), + len(n.spendNotifications), blockHeight) + + for _, tx := range block.Transactions() { + n.filterTx( + block, tx, blockHeight, + n.handleConfDetailsAtTip, + n.handleSpendDetailsAtTip, + ) + } } // Now that we've determined which requests were confirmed and spent @@ -1469,7 +1503,7 @@ func (n *TxNotifier) ConnectTip(blockHash *chainhash.Hash, blockHeight uint32, // filterTx determines whether the transaction spends or confirms any // outstanding pending requests. The onConf and onSpend callbacks can be used to // retrieve all the requests fulfilled by this transaction as they occur. -func (n *TxNotifier) filterTx(tx *btcutil.Tx, blockHash *chainhash.Hash, +func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx, blockHeight uint32, onConf func(ConfRequest, *TxConfirmation), onSpend func(SpendRequest, *SpendDetail)) { @@ -1548,13 +1582,14 @@ func (n *TxNotifier) filterTx(tx *btcutil.Tx, blockHash *chainhash.Hash, notifyDetails := func(confRequest ConfRequest) { Log.Debugf("Found initial confirmation of %v: "+ "height=%d, hash=%v", confRequest, - blockHeight, blockHash) + blockHeight, block.Hash()) details := &TxConfirmation{ Tx: tx.MsgTx(), - BlockHash: blockHash, + BlockHash: block.Hash(), BlockHeight: blockHeight, TxIndex: uint32(tx.Index()), + Block: block.MsgBlock(), } onConf(confRequest, details) @@ -1721,8 +1756,17 @@ func (n *TxNotifier) NotifyHeight(height uint32) error { Log.Infof("Dispatching %v confirmation notification for %v", ntfn.NumConfirmations, ntfn.ConfRequest) + // The default notification we assigned above includes the + // block along with the rest of the details. However not all + // clients want the block, so we make a copy here w/o the block + // if needed so we can give clients only what they ask for. + confDetails := *confSet.details + if !ntfn.includeBlock { + confDetails.Block = nil + } + select { - case ntfn.Event.Confirmed <- confSet.details: + case ntfn.Event.Confirmed <- &confDetails: ntfn.dispatched = true case <-n.quit: return ErrTxNotifierExiting diff --git a/chainntnfs/txnotifier_test.go b/chainntnfs/txnotifier_test.go index 85c502c4f..354c2103b 100644 --- a/chainntnfs/txnotifier_test.go +++ b/chainntnfs/txnotifier_test.go @@ -256,7 +256,7 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) { Transactions: []*wire.MsgTx{&tx1, &tx2}, }) - err = n.ConnectTip(block1.Hash(), 11, block1.Transactions()) + err = n.ConnectTip(block1, 11) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(11); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -316,7 +316,7 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) { // Create a new block and add it to the TxNotifier at the next height. // This should confirm tx2. block2 := btcutil.NewBlock(&wire.MsgBlock{}) - err = n.ConnectTip(block2.Hash(), 12, block2.Transactions()) + err = n.ConnectTip(block2, 12) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(12); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -460,7 +460,7 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) { Transactions: []*wire.MsgTx{&tx3}, }) - err = n.ConnectTip(block.Hash(), 11, block.Transactions()) + err = n.ConnectTip(block, 11) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(11); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -536,7 +536,7 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) { block := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx}, }) - err = n.ConnectTip(block.Hash(), 11, block.Transactions()) + err = n.ConnectTip(block, 11) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(11); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -569,7 +569,7 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) { block = btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendOfSpend}, }) - err = n.ConnectTip(block.Hash(), 12, block.Transactions()) + err = n.ConnectTip(block, 12) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(12); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -609,7 +609,7 @@ func TestTxNotifierFutureConfDispatchReuseSafe(t *testing.T) { Transactions: []*wire.MsgTx{&tx1}, }) currentBlock++ - err = n.ConnectTip(block.Hash(), currentBlock, block.Transactions()) + err = n.ConnectTip(block, currentBlock) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(currentBlock); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -662,7 +662,7 @@ func TestTxNotifierFutureConfDispatchReuseSafe(t *testing.T) { Transactions: []*wire.MsgTx{&tx2}, }) currentBlock++ - err = n.ConnectTip(block2.Hash(), currentBlock, block2.Transactions()) + err = n.ConnectTip(block2, currentBlock) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(currentBlock); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -706,9 +706,7 @@ func TestTxNotifierFutureConfDispatchReuseSafe(t *testing.T) { for currentBlock < 15 { block := btcutil.NewBlock(&wire.MsgBlock{}) currentBlock++ - err = n.ConnectTip( - block.Hash(), currentBlock, block.Transactions(), - ) + err = n.ConnectTip(block, currentBlock) if err != nil { t.Fatalf("unable to connect block: %v", err) } @@ -801,7 +799,7 @@ func TestTxNotifierHistoricalSpendDispatch(t *testing.T) { block := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendOfSpend}, }) - err = n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions()) + err = n.ConnectTip(block, startingHeight+1) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 1); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1110,7 +1108,7 @@ func TestTxNotifierCancelConf(t *testing.T) { // Cancel the second notification before connecting the block. ntfn2.Event.Cancel() - err = n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions()) + err = n.ConnectTip(block, startingHeight+1) require.NoError(t, err, "unable to connect block") // Cancel the third notification before notifying to ensure its queued @@ -1155,7 +1153,7 @@ func TestTxNotifierCancelConf(t *testing.T) { Transactions: []*wire.MsgTx{}, }) - err = n.ConnectTip(block1.Hash(), startingHeight+2, block1.Transactions()) + err = n.ConnectTip(block1, startingHeight+2) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 2); err != nil { @@ -1187,7 +1185,7 @@ func TestTxNotifierCancelConf(t *testing.T) { Transactions: []*wire.MsgTx{}, }) - err = n.ConnectTip(block2.Hash(), startingHeight+3, block2.Transactions()) + err = n.ConnectTip(block2, startingHeight+3) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 3); err != nil { @@ -1241,7 +1239,7 @@ func TestTxNotifierCancelSpend(t *testing.T) { // cancel the second request. n.CancelSpend(ntfn2.HistoricalDispatch.SpendRequest, 2) - err = n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions()) + err = n.ConnectTip(block, startingHeight+1) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 1); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1320,13 +1318,13 @@ func TestTxNotifierConfReorg(t *testing.T) { block1 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{&tx1}, }) - if err := n.ConnectTip(nil, 8, block1.Transactions()); err != nil { + if err := n.ConnectTip(block1, 8); err != nil { t.Fatalf("Failed to connect block: %v", err) } if err := n.NotifyHeight(8); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) } - if err := n.ConnectTip(nil, 9, nil); err != nil { + if err := n.ConnectTip(nil, 9); err != nil { t.Fatalf("Failed to connect block: %v", err) } if err := n.NotifyHeight(9); err != nil { @@ -1336,7 +1334,7 @@ func TestTxNotifierConfReorg(t *testing.T) { block2 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{&tx2, &tx3}, }) - if err := n.ConnectTip(nil, 10, block2.Transactions()); err != nil { + if err := n.ConnectTip(block2, 10); err != nil { t.Fatalf("Failed to connect block: %v", err) } if err := n.NotifyHeight(10); err != nil { @@ -1399,14 +1397,14 @@ func TestTxNotifierConfReorg(t *testing.T) { t.Fatalf("Failed to connect block: %v", err) } - if err := n.ConnectTip(nil, 10, nil); err != nil { + if err := n.ConnectTip(nil, 10); err != nil { t.Fatalf("Failed to connect block: %v", err) } if err := n.NotifyHeight(10); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) } - if err := n.ConnectTip(nil, 11, nil); err != nil { + if err := n.ConnectTip(nil, 11); err != nil { t.Fatalf("Failed to connect block: %v", err) } if err := n.NotifyHeight(11); err != nil { @@ -1456,13 +1454,13 @@ func TestTxNotifierConfReorg(t *testing.T) { }) block4 := btcutil.NewBlock(&wire.MsgBlock{}) - err = n.ConnectTip(block3.Hash(), 12, block3.Transactions()) + err = n.ConnectTip(block3, 12) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(12); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) } - err = n.ConnectTip(block4.Hash(), 13, block4.Transactions()) + err = n.ConnectTip(block4, 13) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(13); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1600,7 +1598,7 @@ func TestTxNotifierSpendReorg(t *testing.T) { block1 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx1}, }) - err = n.ConnectTip(block1.Hash(), startingHeight+1, block1.Transactions()) + err = n.ConnectTip(block1, startingHeight+1) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 1); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1628,7 +1626,7 @@ func TestTxNotifierSpendReorg(t *testing.T) { block2 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx2}, }) - err = n.ConnectTip(block2.Hash(), startingHeight+2, block2.Transactions()) + err = n.ConnectTip(block2, startingHeight+2) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 2); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1680,9 +1678,7 @@ func TestTxNotifierSpendReorg(t *testing.T) { // We'll now extend the chain with an empty block, to ensure that we can // properly detect when an outpoint has been re-spent at a later height. emptyBlock := btcutil.NewBlock(&wire.MsgBlock{}) - err = n.ConnectTip( - emptyBlock.Hash(), startingHeight+2, emptyBlock.Transactions(), - ) + err = n.ConnectTip(emptyBlock, startingHeight+2) require.NoError(t, err, "unable to disconnect block") if err := n.NotifyHeight(startingHeight + 2); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1703,9 +1699,7 @@ func TestTxNotifierSpendReorg(t *testing.T) { // Finally, extend the chain with another block containing the same // spending transaction of the second outpoint. - err = n.ConnectTip( - block2.Hash(), startingHeight+3, block2.Transactions(), - ) + err = n.ConnectTip(block2, startingHeight+3) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 3); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1767,7 +1761,7 @@ func TestTxNotifierSpendReorgMissed(t *testing.T) { block := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx}, }) - err := n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions()) + err := n.ConnectTip(block, startingHeight+1) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(startingHeight + 1); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1875,7 +1869,7 @@ func TestTxNotifierConfirmHintCache(t *testing.T) { Transactions: []*wire.MsgTx{&txDummy}, }) - err = n.ConnectTip(block1.Hash(), txDummyHeight, block1.Transactions()) + err = n.ConnectTip(block1, txDummyHeight) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(txDummyHeight); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1912,7 +1906,7 @@ func TestTxNotifierConfirmHintCache(t *testing.T) { Transactions: []*wire.MsgTx{&tx1}, }) - err = n.ConnectTip(block2.Hash(), tx1Height, block2.Transactions()) + err = n.ConnectTip(block2, tx1Height) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(tx1Height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -1941,7 +1935,7 @@ func TestTxNotifierConfirmHintCache(t *testing.T) { Transactions: []*wire.MsgTx{&tx2}, }) - err = n.ConnectTip(block3.Hash(), tx2Height, block3.Transactions()) + err = n.ConnectTip(block3, tx2Height) require.NoError(t, err, "Failed to connect block") if err := n.NotifyHeight(tx2Height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2037,9 +2031,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) { // Create a new empty block and extend the chain. emptyBlock := btcutil.NewBlock(&wire.MsgBlock{}) - err = n.ConnectTip( - emptyBlock.Hash(), dummyHeight, emptyBlock.Transactions(), - ) + err = n.ConnectTip(emptyBlock, dummyHeight) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(dummyHeight); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2079,7 +2071,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) { block1 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx1}, }) - err = n.ConnectTip(block1.Hash(), op1Height, block1.Transactions()) + err = n.ConnectTip(block1, op1Height) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(op1Height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2108,7 +2100,7 @@ func TestTxNotifierSpendHintCache(t *testing.T) { block2 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx2}, }) - err = n.ConnectTip(block2.Hash(), op2Height, block2.Transactions()) + err = n.ConnectTip(block2, op2Height) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(op2Height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2194,9 +2186,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { // Create a new empty block and extend the chain. height := uint32(startingHeight) + 1 emptyBlock := btcutil.NewBlock(&wire.MsgBlock{}) - err = n.ConnectTip( - emptyBlock.Hash(), height, emptyBlock.Transactions(), - ) + err = n.ConnectTip(emptyBlock, height) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2237,9 +2227,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { block = btcutil.NewBlock(&wire.MsgBlock{}) } - err = n.ConnectTip( - block.Hash(), height, block.Transactions(), - ) + err = n.ConnectTip(block, height) if err != nil { t.Fatalf("unable to connect block: %v", err) } @@ -2290,7 +2278,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { block2 := btcutil.NewBlock(&wire.MsgBlock{ Transactions: []*wire.MsgTx{spendTx2}, }) - err = n.ConnectTip(block2.Hash(), height, block2.Transactions()) + err = n.ConnectTip(block2, height) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(height); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2309,9 +2297,7 @@ func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) { height++ block := btcutil.NewBlock(&wire.MsgBlock{}) - err := n.ConnectTip( - block.Hash(), height, block.Transactions(), - ) + err := n.ConnectTip(block, height) if err != nil { t.Fatalf("unable to connect block: %v", err) } @@ -2368,7 +2354,7 @@ func TestTxNotifierNtfnDone(t *testing.T) { Transactions: []*wire.MsgTx{tx, spendTx}, }) - err = n.ConnectTip(block.Hash(), 11, block.Transactions()) + err = n.ConnectTip(block, 11) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(11); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2418,7 +2404,7 @@ func TestTxNotifierNtfnDone(t *testing.T) { // We'll reconnect the block that satisfies both of these requests. // We should see notifications dispatched for both once again. - err = n.ConnectTip(block.Hash(), 11, block.Transactions()) + err = n.ConnectTip(block, 11) require.NoError(t, err, "unable to connect block") if err := n.NotifyHeight(11); err != nil { t.Fatalf("unable to dispatch notifications: %v", err) @@ -2442,7 +2428,7 @@ func TestTxNotifierNtfnDone(t *testing.T) { nextHeight := uint32(12) for i := nextHeight; i < nextHeight+reorgSafetyLimit; i++ { dummyBlock := btcutil.NewBlock(&wire.MsgBlock{}) - if err := n.ConnectTip(dummyBlock.Hash(), i, nil); err != nil { + if err := n.ConnectTip(dummyBlock, i); err != nil { t.Fatalf("unable to connect block: %v", err) } } diff --git a/chainreg/no_chain_backend.go b/chainreg/no_chain_backend.go index 244fe025e..84019a401 100644 --- a/chainreg/no_chain_backend.go +++ b/chainreg/no_chain_backend.go @@ -57,7 +57,8 @@ func (n *NoChainBackend) RelayFeePerKW() chainfee.SatPerKWeight { } func (n *NoChainBackend) RegisterConfirmationsNtfn(*chainhash.Hash, []byte, - uint32, uint32) (*chainntnfs.ConfirmationEvent, error) { + uint32, uint32, + ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { return nil, errNotImplemented } diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 4b053ba80..8ddde84e3 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -420,7 +420,8 @@ func newMockNotifier() *mockNotifier { } func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - _ []byte, numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) { + _ []byte, numConfs, _ uint32, + opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { return nil, nil } diff --git a/funding/manager_test.go b/funding/manager_test.go index 28eb2e637..93b450a73 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -164,7 +164,7 @@ type mockNotifier struct { } func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + _ []byte, numConfs, heightHint uint32, opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { if numConfs == 6 { return &chainntnfs.ConfirmationEvent{ diff --git a/lntest/mock/chainnotifier.go b/lntest/mock/chainnotifier.go index b7c9377c2..ddce8defa 100644 --- a/lntest/mock/chainnotifier.go +++ b/lntest/mock/chainnotifier.go @@ -16,8 +16,8 @@ type ChainNotifier struct { // RegisterConfirmationsNtfn returns a ConfirmationEvent that contains a channel // that the tx confirmation will go over. func (c *ChainNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - pkScript []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, - error) { + pkScript []byte, numConfs, heightHint uint32, + opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { return &chainntnfs.ConfirmationEvent{ Confirmed: c.ConfChan, diff --git a/sweep/test_utils.go b/sweep/test_utils.go index 475aaf5af..86dfd6d2b 100644 --- a/sweep/test_utils.go +++ b/sweep/test_utils.go @@ -109,8 +109,8 @@ func (m *MockNotifier) sendSpend(channel chan *chainntnfs.SpendDetail, // RegisterConfirmationsNtfn registers for tx confirm notifications. func (m *MockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, - error) { + _ []byte, numConfs, heightHint uint32, + opt ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error) { return &chainntnfs.ConfirmationEvent{ Confirmed: m.getConfChannel(txid), From cdb15e77d57afae0a2593fff78409e58f3dcdf89 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 13 Jul 2022 19:27:31 -0700 Subject: [PATCH 2/4] lnrpc/chainrpc: include the block in the conf ntfn if specified --- lnrpc/chainrpc/chainnotifier.pb.go | 184 ++++++++++++---------- lnrpc/chainrpc/chainnotifier.proto | 12 ++ lnrpc/chainrpc/chainnotifier.swagger.json | 9 ++ lnrpc/chainrpc/chainnotifier_server.go | 22 ++- 4 files changed, 147 insertions(+), 80 deletions(-) diff --git a/lnrpc/chainrpc/chainnotifier.pb.go b/lnrpc/chainrpc/chainnotifier.pb.go index 19317b87b..4480b3137 100644 --- a/lnrpc/chainrpc/chainnotifier.pb.go +++ b/lnrpc/chainrpc/chainnotifier.pb.go @@ -45,6 +45,10 @@ type ConfRequest struct { //could have been included in a block. This should in most cases be set to the //broadcast height of the transaction/output script. HeightHint uint32 `protobuf:"varint,4,opt,name=height_hint,json=heightHint,proto3" json:"height_hint,omitempty"` + // + //If true, then the block that mines the specified txid/script will be + //included in eventual the notification event. + IncludeBlock bool `protobuf:"varint,5,opt,name=include_block,json=includeBlock,proto3" json:"include_block,omitempty"` } func (x *ConfRequest) Reset() { @@ -107,6 +111,13 @@ func (x *ConfRequest) GetHeightHint() uint32 { return 0 } +func (x *ConfRequest) GetIncludeBlock() bool { + if x != nil { + return x.IncludeBlock + } + return false +} + type ConfDetails struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -121,6 +132,10 @@ type ConfDetails struct { BlockHeight uint32 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` // The index of the confirmed transaction within the transaction. TxIndex uint32 `protobuf:"varint,4,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` + // + //The raw bytes of the block that mined the transaction. Only included if + //include_block was set in the request. + RawBlock []byte `protobuf:"bytes,5,opt,name=raw_block,json=rawBlock,proto3" json:"raw_block,omitempty"` } func (x *ConfDetails) Reset() { @@ -183,6 +198,13 @@ func (x *ConfDetails) GetTxIndex() uint32 { return 0 } +func (x *ConfDetails) GetRawBlock() []byte { + if x != nil { + return x.RawBlock + } + return nil +} + type Reorg struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -675,86 +697,90 @@ var File_chainrpc_chainnotifier_proto protoreflect.FileDescriptor var file_chainrpc_chainnotifier_proto_rawDesc = []byte{ 0x0a, 0x1c, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x77, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x73, - 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, 0x6e, - 0x74, 0x22, 0x81, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, - 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x22, 0x6a, - 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x63, - 0x6f, 0x6e, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x27, 0x0a, 0x05, 0x72, 0x65, 0x6f, 0x72, - 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6f, 0x72, - 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a, 0x08, 0x4f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x22, 0x77, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2e, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, - 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, 0x0c, 0x53, 0x70, - 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3f, 0x0a, 0x11, 0x73, 0x70, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, + 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x6e, 0x75, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, + 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x9e, 0x01, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x61, 0x77, 0x5f, 0x74, + 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x61, 0x77, 0x54, 0x78, 0x12, 0x1d, + 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, + 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x72, + 0x61, 0x77, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x72, 0x61, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x07, 0x0a, 0x05, 0x52, 0x65, 0x6f, 0x72, + 0x67, 0x22, 0x6a, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2b, + 0x0a, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, + 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, 0x04, 0x63, 0x6f, 0x6e, 0x66, 0x12, 0x27, 0x0a, 0x05, 0x72, + 0x65, 0x6f, 0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, + 0x65, 0x6f, 0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x34, 0x0a, + 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x77, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x72, - 0x61, 0x77, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x61, 0x77, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x54, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x73, - 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x30, 0x0a, - 0x14, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x73, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x27, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x0a, 0x53, 0x70, 0x65, 0x6e, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x48, 0x00, 0x52, - 0x05, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x27, 0x0a, 0x05, 0x72, 0x65, 0x6f, 0x72, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6f, 0x72, 0x67, 0x42, - 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x32, 0xe7, 0x01, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4e, 0x74, 0x66, - 0x6e, 0x12, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, - 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, - 0x43, 0x0a, 0x11, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x70, 0x65, 0x6e, 0x64, - 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x14, - 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, - 0x70, 0x6f, 0x63, 0x68, 0x1a, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, - 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x68, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x69, 0x6e, 0x74, 0x22, 0xfc, 0x01, 0x0a, + 0x0c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3f, 0x0a, + 0x11, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x73, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x26, + 0x0a, 0x0f, 0x72, 0x61, 0x77, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x61, 0x77, 0x53, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x30, 0x0a, 0x14, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, + 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x6e, 0x0a, 0x0a, 0x53, + 0x70, 0x65, 0x6e, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x48, 0x00, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x12, 0x27, 0x0a, 0x05, 0x72, 0x65, 0x6f, + 0x72, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6f, 0x72, 0x67, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x6f, + 0x72, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x38, 0x0a, 0x0a, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x32, 0xe7, 0x01, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x15, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x30, 0x01, 0x12, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x70, + 0x65, 0x6e, 0x64, 0x4e, 0x74, 0x66, 0x6e, 0x12, 0x16, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x70, 0x65, 0x6e, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x4e, 0x74, 0x66, + 0x6e, 0x12, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x1a, 0x14, 0x2e, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x30, 0x01, 0x42, + 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/chainrpc/chainnotifier.proto b/lnrpc/chainrpc/chainnotifier.proto index 4045d81c3..b5f725fb9 100644 --- a/lnrpc/chainrpc/chainnotifier.proto +++ b/lnrpc/chainrpc/chainnotifier.proto @@ -72,6 +72,12 @@ message ConfRequest { broadcast height of the transaction/output script. */ uint32 height_hint = 4; + + /* + If true, then the block that mines the specified txid/script will be + included in eventual the notification event. + */ + bool include_block = 5; } message ConfDetails { @@ -87,6 +93,12 @@ message ConfDetails { // The index of the confirmed transaction within the transaction. uint32 tx_index = 4; + + /* + The raw bytes of the block that mined the transaction. Only included if + include_block was set in the request. + */ + bytes raw_block = 5; } message Reorg { diff --git a/lnrpc/chainrpc/chainnotifier.swagger.json b/lnrpc/chainrpc/chainnotifier.swagger.json index a1c4b1d2c..0ac139f06 100644 --- a/lnrpc/chainrpc/chainnotifier.swagger.json +++ b/lnrpc/chainrpc/chainnotifier.swagger.json @@ -184,6 +184,11 @@ "type": "integer", "format": "int64", "description": "The index of the confirmed transaction within the transaction." + }, + "raw_block": { + "type": "string", + "format": "byte", + "description": "The raw bytes of the block that mined the transaction. Only included if\ninclude_block was set in the request." } } }, @@ -222,6 +227,10 @@ "type": "integer", "format": "int64", "description": "The earliest height in the chain for which the transaction/output script\ncould have been included in a block. This should in most cases be set to the\nbroadcast height of the transaction/output script." + }, + "include_block": { + "type": "boolean", + "description": "If true, then the block that mines the specified txid/script will be\nincluded in eventual the notification event." } } }, diff --git a/lnrpc/chainrpc/chainnotifier_server.go b/lnrpc/chainrpc/chainnotifier_server.go index 56f6fb8f7..67e6c5057 100644 --- a/lnrpc/chainrpc/chainnotifier_server.go +++ b/lnrpc/chainrpc/chainnotifier_server.go @@ -257,9 +257,14 @@ func (s *Server) RegisterConfirmationsNtfn(in *ConfRequest, var txid chainhash.Hash copy(txid[:], in.Txid) + var opts []chainntnfs.NotifierOption + if in.IncludeBlock { + opts = append(opts, chainntnfs.WithIncludeBlock()) + } + // We'll then register for the spend notification of the request. confEvent, err := s.cfg.ChainNotifier.RegisterConfirmationsNtfn( - &txid, in.Script, in.NumConfs, in.HeightHint, + &txid, in.Script, in.NumConfs, in.HeightHint, opts..., ) if err != nil { return err @@ -284,11 +289,26 @@ func (s *Server) RegisterConfirmationsNtfn(in *ConfRequest, return err } + // If the block was included (should only be there if + // IncludeBlock is true), then we'll encode the bytes + // to send with the response. + var blockBytes []byte + if details.Block != nil { + var blockBuf bytes.Buffer + err := details.Block.Serialize(&blockBuf) + if err != nil { + return err + } + + blockBytes = blockBuf.Bytes() + } + rpcConfDetails := &ConfDetails{ RawTx: rawTxBuf.Bytes(), BlockHash: details.BlockHash[:], BlockHeight: details.BlockHeight, TxIndex: details.TxIndex, + RawBlock: blockBytes, } conf := &ConfEvent{ From 89dc75cd900c89d3c1d4050418a2866ec056670c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 13 Jul 2022 19:34:48 -0700 Subject: [PATCH 3/4] lntest/itest: add test case for conf ntfn w/ block In this commit, we add a test case to exercise the fact that if we request a confirmation notification with the raw block, then it includes one. We add this to the existing test that uses the conf ntfn rpc call for simplicity. --- lntest/itest/lnd_taproot_test.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go index 1b9e6b6a9..20f080404 100644 --- a/lntest/itest/lnd_taproot_test.go +++ b/lntest/itest/lnd_taproot_test.go @@ -1297,12 +1297,16 @@ func confirmAddress(ctx context.Context, t *harnessTest, _, currentHeight, err := net.Miner.Client.GetBestBlock() require.NoError(t.t, err) + + // We'll register for a conf notification, and also request the block + // that included it as well. confClient, err := node.ChainClient.RegisterConfirmationsNtfn( ctx, &chainrpc.ConfRequest{ - Script: addrPkScript, - Txid: txid[:], - HeightHint: uint32(currentHeight), - NumConfs: 1, + Script: addrPkScript, + Txid: txid[:], + HeightHint: uint32(currentHeight), + NumConfs: 1, + IncludeBlock: true, }, ) require.NoError(t.t, err) @@ -1310,12 +1314,18 @@ func confirmAddress(ctx context.Context, t *harnessTest, // Mine another block to clean up the mempool. mineBlocks(t, net, 1, 1) - // We now expect our confirmation to go through. + // We now expect our confirmation to go through, and also that the + // block was specified. confMsg, err := confClient.Recv() require.NoError(t.t, err) conf := confMsg.GetConf() require.NotNil(t.t, conf) require.Equal(t.t, conf.BlockHeight, uint32(currentHeight+1)) + require.NotNil(t.t, conf.RawBlock) + + // We should also be able to decode the raw block. + var blk wire.MsgBlock + require.NoError(t.t, blk.Deserialize(bytes.NewReader(conf.RawBlock))) } // deriveSigningKeys derives three signing keys and returns their descriptors, From 4df0a2240932994cd53d5939efab42ebf803c3b6 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 2 Aug 2022 17:02:43 -0700 Subject: [PATCH 4/4] docs/release-notes: add release notes for 0.16 --- docs/release-notes/release-notes-0.16.0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index 89d27df3f..4d463841a 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -1,5 +1,11 @@ # Release Notes +## RPC + +The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server [now +optionally supports returning the entire block that confirmed the +transaction](https://github.com/lightningnetwork/lnd/pull/6730). + ## Misc * Warning messages from peers are now recognized and [logged](https://github.com/lightningnetwork/lnd/pull/6546) by lnd.