Merge pull request #9677 from NishantBansal2003/conf-count
Expose confirmation count for pending 'channel open' transactions
This commit is contained in:
14 files changed
+2928
-2151
No files matched your search
+18
-3
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user