Merge pull request #9677 from NishantBansal2003/conf-count

Expose confirmation count for pending 'channel open' transactions
This commit is contained in:
Yong authored and GitHub committed 2025-09-11 22:36:05 +08:00
commit 5082566ed7
14 files changed
+2928 -2151

No files matched your search

+18 -3
View File
@@ -203,6 +203,20 @@ type TxConfirmation struct {
Block *wire.MsgBlock
}
// TxUpdateInfo contains information about a transaction before it has reached
// its required number of confirmations. Transactions are registered for
// notification for a specific number of "required" confirmations, this struct
// will update the caller incrementally after each new block is found as long as
// the transaction is not yet fully regarded as confirmed.
type TxUpdateInfo struct {
// BlockHeight is the height of the block that contains the transaction.
BlockHeight uint32
// NumConfsLeft is the number of confirmations left for the transaction
// to be regarded as fully confirmed.
NumConfsLeft uint32
}
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
// callers can be notified of: the instance the target txid reaches the targeted
// number of confirmations, how many confirmations are left for the target txid
@@ -229,11 +243,12 @@ type ConfirmationEvent struct {
// Updates is a channel that will sent upon, at every incremental
// confirmation, how many confirmations are left to declare the
// transaction as fully confirmed.
// transaction as fully confirmed, along with the height of the block
// that contains the transaction.
//
// NOTE: This channel must be buffered with the number of required
// confirmations.
Updates chan uint32
Updates chan TxUpdateInfo
// NegativeConf is a channel that will be sent upon if the transaction
// confirms, but is later reorged out of the chain. The integer sent
@@ -262,7 +277,7 @@ func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
// the channel so we need to create a larger buffer to avoid
// blocking the notifier.
Confirmed: make(chan *TxConfirmation, 1),
Updates: make(chan uint32, numConfs),
Updates: make(chan TxUpdateInfo, numConfs),
NegativeConf: make(chan int32, 1),
Done: make(chan struct{}, 1),
Cancel: cancel,
+42 -12
View File
@@ -947,8 +947,12 @@ func (n *TxNotifier) dispatchConfDetails(
// We'll send a 0 value to the Updates channel,
// indicating that the transaction/output script has already
// been confirmed.
err := n.notifyNumConfsLeft(ntfn, 0)
// been confirmed, and include the block height at which the
// transaction was included.
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: details.BlockHeight,
})
if err != nil {
return err
}
@@ -977,7 +981,10 @@ func (n *TxNotifier) dispatchConfDetails(
// confirmations are left for the transaction/output script to
// be confirmed.
numConfsLeft := confHeight - n.currentHeight
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
NumConfsLeft: numConfsLeft,
BlockHeight: details.BlockHeight,
})
if err != nil {
return err
}
@@ -1731,7 +1738,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
for confRequest := range confRequests {
confSet := n.confNotifications[confRequest]
for _, ntfn := range confSet.ntfns {
txConfHeight := confSet.details.BlockHeight +
// blockHeight is the height of the block which
// contains the transaction.
blockHeight := confSet.details.BlockHeight
txConfHeight := blockHeight +
ntfn.NumConfirmations - 1
numConfsLeft := txConfHeight - height
@@ -1744,7 +1754,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
continue
}
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
NumConfsLeft: numConfsLeft,
BlockHeight: blockHeight,
})
if err != nil {
return err
}
@@ -2011,6 +2024,20 @@ func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
if !ntfn.dispatched {
confHeight := heightDisconnected + ntfn.NumConfirmations - 1
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
// We also signal the reorg to the notifier in case the
// subscriber is also interested in the reorgs before the
// transaction received its required confirmation.
//
// Because as soon as a new block is connected which has the
// transaction included again we preemptively read the buffered
// channel.
select {
case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
case <-n.quit:
return ErrTxNotifierExiting
}
if exists {
delete(ntfnSet, ntfn)
}
@@ -2099,25 +2126,28 @@ func (n *TxNotifier) TearDown() {
}
// notifyNumConfsLeft sends the number of confirmations left to the
// notification subscriber through the Event.Updates channel.
// notification subscriber through the Event.Updates channel, along with the
// block height in which the transaction was included.
//
// NOTE: must be used with the TxNotifier's lock held.
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn,
info TxUpdateInfo) error {
// If the number left is no less than the recorded value, we can skip
// sending it as it means this same value has already been sent before.
if num >= ntfn.numConfsLeft {
if info.NumConfsLeft >= ntfn.numConfsLeft {
Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
"request %v conf_id=%v", num, ntfn.ConfRequest,
ntfn.ConfID)
"request %v conf_id=%v", info.NumConfsLeft,
ntfn.ConfRequest, ntfn.ConfID)
return nil
}
// Update the number of confirmations left to the notification.
ntfn.numConfsLeft = num
ntfn.numConfsLeft = info.NumConfsLeft
select {
case ntfn.Event.Updates <- num:
case ntfn.Event.Updates <- info:
case <-n.quit:
return ErrTxNotifierExiting
}
+91 -49
View File
@@ -271,13 +271,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
// We should only receive one update for tx1 since it only requires
// one confirmation and it already met it.
select {
case numConfsLeft := <-ntfn1.Event.Updates:
const expected = 0
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx1 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn1.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 11,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx1")
}
@@ -300,13 +299,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
// We should only receive one update for tx2 since it only has one
// confirmation so far and it requires two.
select {
case numConfsLeft := <-ntfn2.Event.Updates:
const expected = 1
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx2 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn2.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 1,
BlockHeight: 11,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
}
@@ -341,13 +339,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
// We should only receive one update since the last at the new height,
// indicating how many confirmations are still left.
select {
case numConfsLeft := <-ntfn2.Event.Updates:
const expected = 0
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx2 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn2.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 11,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
}
@@ -411,13 +408,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
err = n.UpdateConfDetails(ntfn1.HistoricalDispatch.ConfRequest, &txConf1)
require.NoError(t, err, "unable to update conf details")
select {
case numConfsLeft := <-ntfn1.Event.Updates:
const expected = 0
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx1 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn1.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 9,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx1")
}
@@ -443,13 +439,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
err = n.UpdateConfDetails(ntfn2.HistoricalDispatch.ConfRequest, &txConf2)
require.NoError(t, err, "unable to update conf details")
select {
case numConfsLeft := <-ntfn2.Event.Updates:
const expected = 1
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx2 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn2.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 1,
BlockHeight: 9,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
}
@@ -485,13 +480,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
// We should only receive one update for tx2 since the last one,
// indicating how many confirmations are still left.
select {
case numConfsLeft := <-ntfn2.Event.Updates:
const expected = 0
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx2 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn2.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 9,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
}
@@ -1490,13 +1484,12 @@ func TestTxNotifierConfReorg(t *testing.T) {
// We should only receive one update for tx2 since it only requires
// one confirmation and it already met it.
select {
case numConfsLeft := <-ntfn2.Event.Updates:
const expected = 0
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx2 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn2.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 12,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
}
@@ -1520,15 +1513,14 @@ func TestTxNotifierConfReorg(t *testing.T) {
// confirmations and it has already met them.
for i := uint32(1); i <= 2; i++ {
select {
case numConfsLeft := <-ntfn3.Event.Updates:
expected := tx3NumConfs - i
if numConfsLeft != expected {
t.Fatalf("Received incorrect confirmation update: tx3 "+
"expected %d confirmations left, got %d",
expected, numConfsLeft)
case updDetails := <-ntfn3.Event.Updates:
expected := chainntnfs.TxUpdateInfo{
NumConfsLeft: tx3NumConfs - i,
BlockHeight: 12,
}
require.Equal(t, expected, updDetails)
default:
t.Fatal("Expected confirmation update for tx2")
t.Fatal("Expected confirmation update for tx3")
}
}
@@ -1548,6 +1540,56 @@ func TestTxNotifierConfReorg(t *testing.T) {
}
}
// TestTxNotifierReorgPartialConfirmation ensures that a tx with intermediate
// confirmations handles a reorg correctly and emits the appropriate reorg ntfn.
func TestTxNotifierReorgPartialConfirmation(t *testing.T) {
t.Parallel()
const txNumConfs uint32 = 2
hintCache := newMockHintCache()
n := chainntnfs.NewTxNotifier(
7, chainntnfs.ReorgSafetyLimit, hintCache, hintCache,
)
// Tx will be confirmed in block 9 and requires 2 confs.
tx := wire.MsgTx{Version: 1}
tx.AddTxOut(&wire.TxOut{PkScript: testRawScript})
txHash := tx.TxHash()
ntfn, err := n.RegisterConf(&txHash, testRawScript, txNumConfs, 1)
require.NoError(t, err, "unable to register ntfn")
err = n.UpdateConfDetails(ntfn.HistoricalDispatch.ConfRequest, nil)
require.NoError(t, err, "unable to deliver conf details")
// Mine 1 block to satisfy the requirement for a partially confirmed tx.
block := btcutil.NewBlock(&wire.MsgBlock{
Transactions: []*wire.MsgTx{&tx},
})
err = n.ConnectTip(block, 8)
require.NoError(t, err, "failed to connect block")
err = n.NotifyHeight(8)
require.NoError(t, err, "unable to dispatch notifications")
// Now that the transaction is partially confirmed, reorg out those
// blocks.
err = n.DisconnectTip(8)
require.NoError(t, err, "unable to disconnect block")
// After the intermediate confirmation is reorged out, the tx should not
// trigger a confirmation ntfn, but should trigger a reorg ntfn.
select {
case <-ntfn.Event.Confirmed:
t.Fatal("unexpected confirmation after reorg")
default:
}
select {
case <-ntfn.Event.NegativeConf:
default:
t.Fatal("expected to receive reorg notification")
}
}
// TestTxNotifierSpendReorg ensures that clients are notified of a reorg when
// the spending transaction of an outpoint for which they registered a spend
// notification for has been reorged out of the chain.
+47
View File
@@ -261,6 +261,10 @@ type openChannelTlvData struct {
// customBlob is an optional TLV encoded blob of data representing
// custom channel funding information.
customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob]
// confirmationHeight records the block height at which the funding
// transaction was first confirmed.
confirmationHeight tlv.RecordT[tlv.TlvType8, uint32]
}
// encode serializes the openChannelTlvData to the given io.Writer.
@@ -270,6 +274,7 @@ func (c *openChannelTlvData) encode(w io.Writer) error {
c.initialLocalBalance.Record(),
c.initialRemoteBalance.Record(),
c.realScid.Record(),
c.confirmationHeight.Record(),
}
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
tlvRecords = append(tlvRecords, memo.Record())
@@ -283,6 +288,8 @@ func (c *openChannelTlvData) encode(w io.Writer) error {
tlvRecords = append(tlvRecords, blob.Record())
})
tlv.SortRecords(tlvRecords)
// Create the tlv stream.
tlvStream, err := tlv.NewStream(tlvRecords...)
if err != nil {
@@ -307,6 +314,7 @@ func (c *openChannelTlvData) decode(r io.Reader) error {
memo.Record(),
tapscriptRoot.Record(),
blob.Record(),
c.confirmationHeight.Record(),
)
if err != nil {
return err
@@ -906,6 +914,10 @@ type OpenChannel struct {
// been confirmed before a certain height.
FundingBroadcastHeight uint32
// ConfirmationHeight records the block height at which the funding
// transaction was first confirmed.
ConfirmationHeight uint32
// NumConfsRequired is the number of confirmations a channel's funding
// transaction must have received in order to be considered available
// for normal transactional use.
@@ -1207,6 +1219,7 @@ func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) {
auxData.initialRemoteBalance.Val,
)
c.confirmedScid = auxData.realScid.Val
c.ConfirmationHeight = auxData.confirmationHeight.Val
auxData.memo.WhenSomeV(func(memo []byte) {
c.Memo = memo
@@ -1234,6 +1247,9 @@ func (c *OpenChannel) extractTlvData() openChannelTlvData {
realScid: tlv.NewRecordT[tlv.TlvType4](
c.confirmedScid,
),
confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8](
c.ConfirmationHeight,
),
}
if len(c.Memo) != 0 {
@@ -1501,6 +1517,37 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
return putOpenChannel(chanBucket, c)
}
// MarkConfirmationHeight updates the channel's confirmation height once the
// channel opening transaction receives one confirmation.
func (c *OpenChannel) MarkConfirmationHeight(height uint32) error {
c.Lock()
defer c.Unlock()
if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
if err != nil {
return err
}
channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
if err != nil {
return err
}
channel.ConfirmationHeight = height
return putOpenChannel(chanBucket, channel)
}, func() {}); err != nil {
return err
}
c.ConfirmationHeight = height
return nil
}
// MarkAsOpen marks a channel as fully open given a locator that uniquely
// describes its location within the chain.
func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
+68 -1
View File
@@ -978,6 +978,73 @@ func TestChannelStateTransition(t *testing.T) {
require.Empty(t, fwdPkgs, "no forwarding packages should exist")
}
// TestOpeningChannelTxConfirmation verifies that calling MarkConfirmationHeight
// correctly updates the confirmed state. It also ensures that calling Refresh
// on a different OpenChannel updates its in-memory state to reflect the prior
// MarkConfirmationHeight call.
func TestOpeningChannelTxConfirmation(t *testing.T) {
t.Parallel()
fullDB, err := MakeTestDB(t)
require.NoError(t, err)
cdb := fullDB.ChannelStateDB()
// Create a pending channel that was broadcast at height 99.
const broadcastHeight = uint32(99)
channelState := createTestChannel(
t, cdb, pendingHeightOption(broadcastHeight),
)
// Fetch pending channels from the database.
pendingChannels, err := cdb.FetchPendingChannels()
require.NoError(t, err)
require.Len(t, pendingChannels, 1)
// Verify the broadcast height of the pending channel.
require.Equal(
t, broadcastHeight, pendingChannels[0].FundingBroadcastHeight,
)
confirmationHeight := broadcastHeight + 1
// Mark the channel's confirmation height.
err = pendingChannels[0].MarkConfirmationHeight(confirmationHeight)
require.NoError(t, err)
// Verify the ConfirmationHeight is updated correctly.
require.Equal(
t, confirmationHeight, pendingChannels[0].ConfirmationHeight,
)
// Re-fetch the pending channels to confirm persistence.
pendingChannels, err = cdb.FetchPendingChannels()
require.NoError(t, err)
require.Len(t, pendingChannels, 1)
// Validate the confirmation and broadcast height.
require.Equal(
t, confirmationHeight, pendingChannels[0].ConfirmationHeight,
)
require.Equal(
t, broadcastHeight, pendingChannels[0].FundingBroadcastHeight,
)
// Ensure the original channel state's confirmation height is not
// updated before refresh.
require.EqualValues(t, channelState.ConfirmationHeight, 0)
// Refresh the original channel state.
err = channelState.Refresh()
require.NoError(t, err)
// Verify that both channel states now have the same ConfirmationHeight.
require.Equal(
t, channelState.ConfirmationHeight,
pendingChannels[0].ConfirmationHeight,
)
}
func TestFetchPendingChannels(t *testing.T) {
t.Parallel()
@@ -1007,7 +1074,7 @@ func TestFetchPendingChannels(t *testing.T) {
}
chanOpenLoc := lnwire.ShortChannelID{
BlockHeight: 5,
BlockHeight: broadcastHeight + 1,
TxIndex: 10,
TxPosition: 15,
}
@@ -56,6 +56,14 @@ reflect their state on the channel commitment without having to send or receive
a certain amount of msats.
## Functional Enhancements
* [Add](https://github.com/lightningnetwork/lnd/pull/9677)
`ConfirmationsUntilActive` and `ConfirmationHeight` field to the
`PendingChannelsResponse_PendingChannel` message, providing users with the
number of confirmations remaining before a pending channel becomes active and
the block height at which the funding transaction was first confirmed.
This change also persists the channel's confirmation height in the database
once its funding transaction receives one confirmation, allowing tracking of
confirmation progress before the channel becomes active.
* RPCs `walletrpc.EstimateFee` and `walletrpc.FundPsbt` now
[allow](https://github.com/lightningnetwork/lnd/pull/10087)
+128 -24
View File
@@ -3065,7 +3065,8 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
// process once the funding transaction has been broadcast. The primary
// function of waitForFundingConfirmation is to wait for blockchain
// confirmation, and then to notify the other systems that must be notified
// when a channel has become active for lightning transactions.
// when a channel has become active for lightning transactions. It also updates
// the channel’s opening transaction block height in the database.
// The wait can be canceled by closing the cancelChan. In case of success,
// a *lnwire.ShortChannelID will be passed to confChan.
//
@@ -3109,34 +3110,135 @@ func (f *Manager) waitForFundingConfirmation(
log.Infof("Waiting for funding tx (%v) to reach %v confirmations",
txid, numConfs)
var confDetails *chainntnfs.TxConfirmation
var ok bool
// Wait until the specified number of confirmations has been reached,
// we get a cancel signal, or the wallet signals a shutdown.
select {
case confDetails, ok = <-confNtfn.Confirmed:
// fallthrough
for {
select {
case updDetails, ok := <-confNtfn.Updates:
if !ok {
log.Warnf("ChainNotifier shutting down, "+
"cannot process updates for "+
"ChannelPoint(%v)",
completeChan.FundingOutpoint)
case <-cancelChan:
log.Warnf("canceled waiting for funding confirmation, "+
"stopping funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
return
}
case <-f.quit:
log.Warnf("fundingManager shutting down, stopping funding "+
"flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
log.Debugf("funding tx %s received confirmation in "+
"block %d, %d confirmations left", txid,
updDetails.BlockHeight, updDetails.NumConfsLeft)
// Only update the ConfirmationHeight the first time a
// confirmation is received, since on subsequent
// confirmations the block height will remain the same.
if completeChan.ConfirmationHeight == 0 {
err := completeChan.MarkConfirmationHeight(
updDetails.BlockHeight,
)
if err != nil {
log.Errorf("failed to update "+
"confirmed state for "+
"ChannelPoint(%v): %v",
completeChan.FundingOutpoint,
err)
return
}
}
case _, ok := <-confNtfn.NegativeConf:
if !ok {
log.Warnf("ChainNotifier shutting down, "+
"cannot track negative confirmations "+
"for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
log.Warnf("funding tx %s was reorged out; channel "+
"point: %s", txid, completeChan.FundingOutpoint)
// Reset the confirmation height to 0 because the
// funding transaction was reorged out.
err := completeChan.MarkConfirmationHeight(uint32(0))
if err != nil {
log.Errorf("failed to update state for "+
"ChannelPoint(%v): %v",
completeChan.FundingOutpoint, err)
return
}
case confDetails, ok := <-confNtfn.Confirmed:
if !ok {
log.Warnf("ChainNotifier shutting down, "+
"cannot complete funding flow for "+
"ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
log.Debugf("funding tx %s for ChannelPoint(%v) "+
"confirmed in block %d", txid,
completeChan.FundingOutpoint,
confDetails.BlockHeight)
// In the case of requiring a single confirmation, it
// can happen that the `Confirmed` channel is read
// from first, in which case the confirmation height
// will not be set. If this happens, we take the
// confirmation height from the `Confirmed` channel.
if completeChan.ConfirmationHeight == 0 {
err := completeChan.MarkConfirmationHeight(
confDetails.BlockHeight,
)
if err != nil {
log.Errorf("failed to update "+
"confirmed state for "+
"ChannelPoint(%v): %v",
completeChan.FundingOutpoint,
err)
return
}
}
err := f.handleConfirmation(
confDetails, completeChan, confChan,
)
if err != nil {
log.Errorf("Error handling confirmation for "+
"ChannelPoint(%v), txid=%v: %v",
completeChan.FundingOutpoint, txid, err)
}
return
case <-cancelChan:
log.Warnf("canceled waiting for funding confirmation, "+
"stopping funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
case <-f.quit:
log.Warnf("fundingManager shutting down, stopping "+
"funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
}
}
if !ok {
log.Warnf("ChainNotifier shutting down, cannot complete "+
"funding flow for ChannelPoint(%v)",
completeChan.FundingOutpoint)
return
}
// handleConfirmation is a helper function that constructs a ShortChannelID
// based on the confirmation details and sends this information, along with the
// funding transaction, to the provided confirmation channel.
func (f *Manager) handleConfirmation(confDetails *chainntnfs.TxConfirmation,
completeChan *channeldb.OpenChannel,
confChan chan<- *confirmedChannel) error {
fundingPoint := completeChan.FundingOutpoint
log.Infof("ChannelPoint(%v) is now active: ChannelID(%v)",
@@ -3157,8 +3259,10 @@ func (f *Manager) waitForFundingConfirmation(
fundingTx: confDetails.Tx,
}:
case <-f.quit:
return
return fmt.Errorf("manager shutting down")
}
return nil
}
// waitForTimeout will close the timeout channel if MaxWaitNumBlocksFundingConf
+197 -81
View File
@@ -178,9 +178,11 @@ func (m *mockAliasMgr) DeleteSixConfs(lnwire.ShortChannelID) error {
}
type mockNotifier struct {
oneConfChannel chan *chainntnfs.TxConfirmation
sixConfChannel chan *chainntnfs.TxConfirmation
epochChan chan *chainntnfs.BlockEpoch
oneConfChannel chan *chainntnfs.TxConfirmation
sixConfChannel chan *chainntnfs.TxConfirmation
epochChan chan *chainntnfs.BlockEpoch
oneUpdateChannel chan chainntnfs.TxUpdateInfo
reOrgChan chan int32
}
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
@@ -190,11 +192,14 @@ func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
if numConfs == 6 {
return &chainntnfs.ConfirmationEvent{
Confirmed: m.sixConfChannel,
Confirmed: m.sixConfChannel,
NegativeConf: m.reOrgChan,
}, nil
}
return &chainntnfs.ConfirmationEvent{
Confirmed: m.oneConfChannel,
Confirmed: m.oneConfChannel,
Updates: m.oneUpdateChannel,
NegativeConf: m.reOrgChan,
}, nil
}
@@ -400,9 +405,11 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
estimator := chainfee.NewStaticEstimator(62500, 0)
chainNotifier := &mockNotifier{
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
sixConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
sixConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
oneUpdateChannel: make(chan chainntnfs.TxUpdateInfo, 1),
reOrgChan: make(chan int32, 1),
}
aliasMgr := &mockAliasMgr{}
@@ -1090,6 +1097,37 @@ func assertNumPendingChannelsRemains(t *testing.T, node *testNode,
}
}
// assertConfirmationHeight checks that the channel with the given chanID has
// the expected confirmation height in the database. It will retry for a few
// times in case the confirmation height is not yet set in the database.
func assertConfirmationHeight(t *testing.T, node *testNode,
chanID lnwire.ChannelID, expectedConfHeight uint32) {
t.Helper()
err := wait.NoError(func() error {
pendingChannel, err := node.fundingMgr.cfg.Wallet.Cfg.Database.
FetchChannelByID(nil, chanID)
if err != nil {
return fmt.Errorf("unable to fetch pending channel: %w",
err)
}
// Check if the confirmation height is as expected.
actualConfHeight := pendingChannel.ConfirmationHeight
if actualConfHeight != expectedConfHeight {
return fmt.Errorf("Expected node to have %d "+
"confirmation height, had %v",
expectedConfHeight, actualConfHeight)
}
// Success, return.
return nil
}, wait.DefaultTimeout)
require.NoError(t, err)
}
func assertDatabaseState(t *testing.T, node *testNode,
fundingOutPoint *wire.OutPoint, expectedState channelOpeningState) {
@@ -1447,6 +1485,98 @@ func assertHandleChannelReady(t *testing.T, alice, bob *testNode,
}
}
// sendAndCheckFirstConfirmation sends a transaction confirmation update to the
// given test node and verifies that the confirmation height has been set to 1
// for the specified channel. This is used when the required number of
// confirmations is a single block.
func sendAndCheckFirstConfirmation(t *testing.T, node *testNode,
chanID lnwire.ChannelID, fundingTx *wire.MsgTx) {
t.Helper()
// Send an update that the transaction has been confirmed.
node.mockNotifier.oneUpdateChannel <- chainntnfs.TxUpdateInfo{
NumConfsLeft: 0,
BlockHeight: 1,
}
// Notify the node that the transaction was mined at block height 1.
node.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
BlockHeight: 1,
}
// Verify that the confirmation height is correctly set to 1 for the
// given node and channel ID.
assertConfirmationHeight(t, node, chanID, 1)
}
// TestFundingManagerTxReorg verifies that when the funding transaction is
// reorged out of the chain, the channel's confirmation height resets to zero,
// and that re-confirmation proceed as normal.
func TestFundingManagerTxReorg(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
fundingOutPoint, _ := openChannel(t, alice, bob, 500000, 0, 3,
updateChan, true, nil)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Send an update that the transaction has received confirmation.
alice.mockNotifier.oneUpdateChannel <- chainntnfs.TxUpdateInfo{
BlockHeight: 1,
NumConfsLeft: 2,
}
bob.mockNotifier.oneUpdateChannel <- chainntnfs.TxUpdateInfo{
BlockHeight: 1,
NumConfsLeft: 2,
}
// Check that the confirmation height is set to 1 for both alice and
// bob.
assertConfirmationHeight(t, alice, chanID, 1)
assertConfirmationHeight(t, bob, chanID, 1)
// Now we'll simulate a reorg of the funding transaction. This will
// cause the confirmation height to be set to 0.
alice.mockNotifier.reOrgChan <- 1
bob.mockNotifier.reOrgChan <- 1
// Check that the confirmation height is set to 0 for both alice and
// bob.
assertConfirmationHeight(t, alice, chanID, 0)
assertConfirmationHeight(t, bob, chanID, 0)
// Since the transaction is not confirmerd, there should be no channel
// state in the database.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// Send an update that the transaction has been again confirmed.
alice.mockNotifier.oneUpdateChannel <- chainntnfs.TxUpdateInfo{
BlockHeight: 3,
NumConfsLeft: 2,
}
bob.mockNotifier.oneUpdateChannel <- chainntnfs.TxUpdateInfo{
BlockHeight: 3,
NumConfsLeft: 2,
}
// Check that the confirmation height is set to 3 for both alice and
// bob.
assertConfirmationHeight(t, alice, chanID, 3)
assertConfirmationHeight(t, bob, chanID, 3)
}
func testNormalWorkflow(t *testing.T, chanType *lnwire.ChannelType) {
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
@@ -1485,18 +1615,16 @@ func testNormalWorkflow(t *testing.T, chanType *lnwire.ChannelType) {
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
chanType,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Check that neither Alice nor Bob sent an error message.
assertErrorNotSent(t, alice.msgChan)
assertErrorNotSent(t, bob.msgChan)
// Notify that transaction was mined.
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -1784,6 +1912,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// After the funding transaction gets mined, both nodes will send the
// channelReady message to the other peer. If the funding node fails
@@ -1803,13 +1932,10 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
}
alice.fundingMgr.cfg.NotifyWhenOnline = notifyWhenOnline
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -1943,6 +2069,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// After the funding transaction gets mined, both nodes will send the
// channelReady message to the other peer. If the funding node fails
@@ -1963,13 +2090,10 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
conChan <- connected
}
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -2458,14 +2582,12 @@ func TestFundingManagerReceiveChannelReadyTwice(t *testing.T) {
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -2571,14 +2693,12 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -2670,14 +2790,12 @@ func TestFundingManagerRestartAfterReceivingChannelReady(t *testing.T) {
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -2765,14 +2883,12 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -2890,14 +3006,12 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
)
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
@@ -3333,13 +3447,10 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
t.Fatalf("alice did not publish funding tx")
}
// Notify that transaction was mined.
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, fundingSigned.ChanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, fundingSigned.ChanID, fundingTx)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
@@ -4550,6 +4661,7 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) {
Hash: fundingTx.TxHash(),
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(*fundingOp)
// Assert that Bob's channel_ready message has an AliasScid.
bobChannelReady, ok := assertFundingMsgSent(
@@ -4598,12 +4710,19 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) {
// We'll now confirm the funding transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
Tx: fundingTx,
BlockHeight: 1,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
Tx: fundingTx,
BlockHeight: 1,
}
// Check that the confirmation height is set to 1 for both alice and
// bob.
assertConfirmationHeight(t, alice, chanID, 1)
assertConfirmationHeight(t, bob, chanID, 1)
// For taproot channels, we don't expect them to be announced atm.
if !isTaprootChanType(chanType) {
assertChannelAnnouncements(
@@ -4833,6 +4952,7 @@ func TestFundingManagerCoinbase(t *testing.T) {
Hash: fundingTx.TxHash(),
Index: 0,
}
chanID := lnwire.NewChanIDFromOutPoint(*fundingOp)
chanFunder := &mockChanFunder{
fundingAmt: chanSize,
@@ -4909,14 +5029,10 @@ func TestFundingManagerCoinbase(t *testing.T) {
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
require.True(t, ok)
// Confirm the funding transaction.
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Notify that the transaction was mined, and check that the
// confirmation height is set to 1 for both Alice and Bob.
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
// Make sure the notification about the pending channel was sent out.
select {
+8
View File
@@ -62,6 +62,14 @@ var allTestCases = []*lntest.TestCase{
Name: "funding expiry blocks on pending",
TestFunc: testFundingExpiryBlocksOnPending,
},
{
Name: "pending channel confirmation until active",
TestFunc: testPendingChannelConfirmationUntilActive,
},
{
Name: "pending channel reorg test",
TestFunc: testPendingChannelAfterReorg,
},
{
Name: "list channels",
TestFunc: testListChannels,
+230
View File
@@ -877,6 +877,236 @@ func testFundingExpiryBlocksOnPending(ht *lntest.HarnessTest) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// assertConfirmation is a helper to assert the ConfirmationsUntilActive and
// ConfirmationHeight has been updated to the expected value.
func assertConfirmation(ht *lntest.HarnessTest, hn *node.HarnessNode,
expConfLeft, expConfHeight uint32) {
ht.Helper()
err := wait.NoError(func() error {
// Node should have one pending open channel.
pendingChan := ht.AssertNumPendingOpenChannels(hn, 1)[0]
// Check if the ConfirmationsUntilActive is updated to the
// expected value.
if expConfLeft != pendingChan.ConfirmationsUntilActive {
return fmt.Errorf("remaining confirmations mismatch, "+
"want %v, got %v", expConfLeft,
pendingChan.ConfirmationsUntilActive)
}
// Check if the ConfirmationHeight is updated to the expected
// value.
if expConfHeight != pendingChan.ConfirmationHeight {
return fmt.Errorf("confirmation height mismatch, want "+
"%v, got %v", expConfHeight,
pendingChan.ConfirmationHeight)
}
return nil
}, defaultTimeout)
require.NoError(ht, err)
}
// testPendingChannelConfirmationUntilActive verifies the value for the rpc
// field ConfirmationUntilActive updates correctly as soon as blocks are
// confirmed.
func testPendingChannelConfirmationUntilActive(ht *lntest.HarnessTest) {
var (
numConfs uint32 = 5
chanAmt btcutil.Amount = 100000
)
// Since we want Bob's channels to require more than 1 on-chain
// confirmation before becoming active, we will launch Bob with the
// custom defaultchanconfs flag.
alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNode("Bob", []string{
fmt.Sprintf("--bitcoin.defaultchanconfs=%v", numConfs),
})
// Ensure Alice and Bob are connected.
ht.EnsureConnected(alice, bob)
// Alice initiates a channel opening to Bob.
param := lntest.OpenChannelParams{Amt: chanAmt}
ht.OpenChannelAssertPending(alice, bob, param)
// Both Alice and Bob have one pending open channel.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Since the funding transaction is not confirmed yet,
// ConfirmationsUntilActive will always be numConfs, and confirmation
// height will be 0.
assertConfirmation(ht, alice, numConfs, 0)
assertConfirmation(ht, bob, numConfs, 0)
// Mine the first block containing the funding transaction, This
// confirms the funding transaction but the channel should still remain
// pending.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Decrement numConfs to reflect that one confirmation has been
// received.
numConfs--
// Since the funding transaction has been mined, the best block height
// corresponds to the confirmation height of the channel's opening tx.
_, expConfHeight := ht.GetBestBlock()
// Channel remains pending after the first confirmation.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Make sure the ConfirmationsUntilActive and ConfirmationHeight
// fields have been updated to the expected values before restarting the
// nodes.
assertConfirmation(ht, alice, numConfs, uint32(expConfHeight))
assertConfirmation(ht, bob, numConfs, uint32(expConfHeight))
// Restart both nodes to test that the appropriate state has been
// persisted and that both nodes recover gracefully.
ht.RestartNode(alice)
ht.RestartNode(bob)
ht.EnsureConnected(alice, bob)
// ConfirmationsUntilActive field should decrease as each block is
// mined until the required number of confirmations is reached. Let's
// mine a few blocks and verify the value of ConfirmationsUntilActive at
// each step.
for i := numConfs; i > 0; i-- {
expConfLeft := i
// Retrieve pending channels for both Alice and Bob and verify
// the remaining confirmations and confirmation height.
assertConfirmation(
ht, alice, expConfLeft, uint32(expConfHeight),
)
assertConfirmation(ht, bob, expConfLeft, uint32(expConfHeight))
// Mine the next block.
ht.MineBlocks(1)
}
// After the required number of confirmations, the channel should be
// marked as active.
ht.AssertNumPendingOpenChannels(alice, 0)
ht.AssertNumPendingOpenChannels(bob, 0)
}
// testPendingChannelAfterReorg verifies the value for the rpc field
// ConfirmationUntilActive updates correctly as blocks are confirmed and after
// chain reorgs.
func testPendingChannelAfterReorg(ht *lntest.HarnessTest) {
// Skip test for neutrino, as we cannot disconnect the miner at will.
if ht.IsNeutrinoBackend() {
ht.Skipf("skipping reorg test for neutrino backend")
}
var numConfs uint32 = 3
// Since we want Bob's channels to require more than 1 on-chain
// confirmation before becoming active, we will launch Bob with the
// custom defaultchanconfs flag.
miner := ht.Miner()
alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNode("Bob", []string{
fmt.Sprintf("--bitcoin.defaultchanconfs=%v", numConfs),
})
ht.EnsureConnected(alice, bob)
// Spawn a temporary miner to simulate a chain reorg with a longer
// chain.
tempMiner := ht.SpawnTempMiner()
// Alice initiates a channel opening to Bob.
params := lntest.OpenChannelParams{Amt: funding.MaxBtcFundingAmount}
ht.OpenChannelAssertPending(alice, bob, params)
// Mine the first block containing the funding transaction.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Channel remains pending after the first confirmation.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Since the funding transaction has been mined, the best block height
// corresponds to the confirmation height of the channel's opening tx.
_, expConfHeight := ht.GetBestBlock()
// Make sure the ConfirmationsUntilActive and ConfirmationHeight
// fields have been updated to the expected values before reorg.
//
// Decrement numConfs to reflect one confirmation received.
assertConfirmation(ht, alice, numConfs-1, uint32(expConfHeight))
assertConfirmation(ht, bob, numConfs-1, uint32(expConfHeight))
// We now cause a fork, by letting our original miner mine 1 blocks,
// and our new miner mine 3.
_, err := tempMiner.Client.Generate(3)
require.NoError(ht, err, "unable to generate blocks on temp miner")
// Ensure the chain lengths are what we expect, with the temp miner
// being 2 blocks ahead.
miner.AssertMinerBlockHeightDelta(tempMiner, 2)
// Now we disconnect Alice's chain backend from the original miner, and
// connect the two miners together. Since the temporary miner knows
// about a longer chain, both miners should sync to that chain.
ht.DisconnectMiner()
// Connecting to the temporary miner should now cause our original
// chain to be re-orged out.
miner.ConnectMiner(tempMiner)
// Once again they should be on the same chain.
miner.AssertMinerBlockHeightDelta(tempMiner, 0)
// Now we disconnect the two miners, and connect our original miner to
// our chain backend once again.
miner.DisconnectMiner(tempMiner)
ht.ConnectMiner()
// This should have caused a reorg, and Alice should sync to the longer
// chain, where the funding transaction is not confirmed.
_, tempMinerHeight, err := tempMiner.Client.GetBestBlock()
require.NoError(ht, err, "unable to get current blockheight")
ht.WaitForNodeBlockHeight(alice, tempMinerHeight)
// After the reorg, the funding transaction's confirmation is removed,
// so the pending channel should again require the original number of
// confirmations and have a confirmation height of 0.
assertConfirmation(ht, alice, numConfs, 0)
assertConfirmation(ht, bob, numConfs, 0)
// Mine the first block containing the funding transaction again.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Decrement numConfs to reflect one confirmation received.
numConfs--
// Since the funding transaction has been mined, the best block height
// corresponds to the confirmation height of the channel's opening tx.
_, expConfHeight = ht.GetBestBlock()
// Make sure the ConfirmationsUntilActive and ConfirmationHeight
// fields have been updated to the expected values after reorg.
assertConfirmation(ht, alice, numConfs, uint32(expConfHeight))
assertConfirmation(ht, bob, numConfs, uint32(expConfHeight))
// Cleanup by mining the remaining blocks to reach the required number
// of confirmations.
ht.MineBlocks(int(numConfs))
// After the required number of confirmations, the channel should be
// marked as active.
ht.AssertNumPendingOpenChannels(alice, 0)
ht.AssertNumPendingOpenChannels(bob, 0)
}
// testSimpleTaprootChannelActivation ensures that a simple taproot channel is
// active if the initiator disconnects and reconnects in between channel opening
// and channel confirmation.
+2014 -1976
View File
File diff suppressed because it is too large. Load diff
+19
View File
@@ -2848,6 +2848,25 @@ message PendingChannelsResponse {
// very likely canceled the funding and the channel will never become
// fully operational.
int32 funding_expiry_blocks = 3;
// The number of blocks remaining until the channel status changes from
// pending to active. A value of 0 indicates that the channel is now
// active.
//
// "Active" here means both channel peers have the channel marked OPEN
// and can immediately start using it. For public channels, this does
// not imply a channel_announcement has been gossiped. It only becomes
// public on the network after 6 on‐chain confirmations.
// See BOLT07 "Routing Gossip":
// https://github.com/lightning/bolts/blob/master/07-routing-gossip.md
//
// ZeroConf channels bypass the pending state entirely: they are marked
// active immediately upon creation, so they never show up as "pending".
uint32 confirmations_until_active = 7;
// The confirmation height records the block height at which the funding
// transaction was first confirmed.
uint32 confirmation_height = 8;
}
message WaitingCloseChannel {
+10
View File
@@ -3337,6 +3337,16 @@
"type": "integer",
"format": "int32",
"description": "The number of blocks until the funding transaction is considered\nexpired. If this value gets close to zero, there is a risk that the\nchannel funding will be canceled by the channel responder. The\nchannel should be fee bumped using CPFP (see walletrpc.BumpFee) to\nensure that the channel confirms in time. Otherwise a force-close\nwill be necessary if the channel confirms after the funding\ntransaction expires. A negative value means the channel responder has\nvery likely canceled the funding and the channel will never become\nfully operational."
},
"confirmations_until_active": {
"type": "integer",
"format": "int64",
"description": "The number of blocks remaining until the channel status changes from\npending to active. A value of 0 indicates that the channel is now\nactive.\n\n\"Active\" here means both channel peers have the channel marked OPEN\nand can immediately start using it. For public channels, this does\nnot imply a channel_announcement has been gossiped. It only becomes\npublic on the network after 6 on‐chain confirmations.\nSee BOLT07 \"Routing Gossip\":\nhttps://github.com/lightning/bolts/blob/master/07-routing-gossip.md\n\nZeroConf channels bypass the pending state entirely: they are marked\nactive immediately upon creation, so they never show up as \"pending\"."
},
"confirmation_height": {
"type": "integer",
"format": "int64",
"description": "The confirmation height records the block height at which the funding\ntransaction was first confirmed."
}
}
},
+48 -5
View File
@@ -3889,6 +3889,37 @@ type (
waitingCloseChannels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel
)
// calcRemainingConfs calculates how many more confirmations are needed for a
// pending channel to be fully confirmed. It takes into account:
// 1. The current blockchain height
// 2. The block height at which the funding transaction was first confirmed
// 3. The total number of confirmations required for the channel.
func calcRemainingConfs(pendingChan *channeldb.OpenChannel,
currentHeight uint32) uint32 {
// If the funding transaction hasn't been confirmed yet,
// we need all the required confirmations.
if pendingChan.ConfirmationHeight == 0 {
return uint32(pendingChan.NumConfsRequired)
}
// Calculate the target height at which the channel will be fully
// confirmed. The -1 is because the confirmation height of the first
// confirmation has to be taken into account.
targetConfirmationHeight := pendingChan.ConfirmationHeight +
uint32(pendingChan.NumConfsRequired) - 1
// In case the current height is already past the target, return 0. This
// should never happen because the channel should already be moved from
// pending to open state but we handle this case in case of timing
// issues.
if currentHeight >= targetConfirmationHeight {
return 0
}
return targetConfirmationHeight - currentHeight
}
// fetchPendingOpenChannels queries the database for a list of channels that
// have pending open state. The returned result is used in the response of the
// PendingChannels RPC.
@@ -3948,6 +3979,15 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) {
pendingChan.BroadcastHeight()
fundingExpiryBlocks := int32(maxFundingHeight) - currentHeight
// Calculate remainingConfs, the number of blocks left until the
// funding transaction reaches the required confirmation height.
//
// ZeroConf channels are marked OPEN immediately upon creation,
// so they never enter the "pending" state.
remainingConfs := calcRemainingConfs(
pendingChan, uint32(currentHeight),
)
customChanBytes, err := encodeCustomChanData(pendingChan)
if err != nil {
return nil, fmt.Errorf("unable to encode open chan "+
@@ -3969,11 +4009,14 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) {
Memo: string(pendingChan.Memo),
CustomChannelData: customChanBytes,
},
CommitWeight: commitWeight,
CommitFee: int64(localCommitment.CommitFee),
FeePerKw: int64(localCommitment.FeePerKw),
FundingExpiryBlocks: fundingExpiryBlocks,
// TODO(roasbeef): need to track confirmation height
CommitWeight: commitWeight,
CommitFee: int64(localCommitment.CommitFee),
FeePerKw: int64(localCommitment.
FeePerKw),
FundingExpiryBlocks: fundingExpiryBlocks,
ConfirmationsUntilActive: remainingConfs,
ConfirmationHeight: pendingChan.
ConfirmationHeight,
}
}