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/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. 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/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{ 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, 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),